diff --git a/.gitignore b/.gitignore index 26355427d0c02cb281a52bf083e1e847bcdf45bc..e55fbc5d873963eb372b33a7b8dbed20b099f0ee 100644 --- a/.gitignore +++ b/.gitignore @@ -121,6 +121,9 @@ tmp/ # TeXlipse plugin .texlipseXml version="1.0" encoding="UTF-8"?> -gradle.properties +/gradle.properties sign.properties -app/oschina.keystore + +captures/* + +gradle/wrapper/gradle-wrapper.properties diff --git a/.inputrc.save b/.inputrc.save new file mode 100644 index 0000000000000000000000000000000000000000..a1ccdf829a99a0222224a5fd4aa7eb499148686a --- /dev/null +++ b/.inputrc.save @@ -0,0 +1,3 @@ +set completion-ignore-case on +set show-all-if-ambiguous on +TAB:menu-complete diff --git a/README.md b/README.md index 997af8c75b136362fddbcaf659619b5b3833fdd2..7e780ae4a0f8400ea808bf2a97f8b35c608299c0 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,28 @@ -# OSChina Android [客户端](http://www.oschina.net/app/) +##提示 +- 新版界面实现详见:net.oschina.app.improve包。 +- OSChina Android [客户端](http://www.oschina.net/app/) -##写在前面的话 -从2.3版本开始,项目已经完成了gradle化,完全迁移到了android studio,如果想使用eclipse进行该项目的学习,可以clone [tag v2.2.1](http://git.oschina.net/oschina/android-app/tree/v2.2.1/),不过需要注意的是,eclipse需要按照开发环境中提到的:进行butterknife注解设置 -##开发环境 -由于使用了较多的Eclipse项目Library,项目目前使用的是Eclipse。需要提示的是,由于butterknife注解特性,Eclipse需要开启注解功能,详细方法参考[这里](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0102/2247.html)。对于使用Android Studio的开发者,可能你们需要等待一段时间,项目目前正在Gradle化。当然,我们也欢迎由你来转换项目并通过PullRequest提交给我们,充分发挥社区化协作的优势。 - -##项目简述 -1. 底部导航 - * 主界面的底部TAB导航采用[FragmentTabHost](http://git.oschina.net/oschina/osc-android-app/blob/master/osc-android-app/src/net/oschina/app/ui/MainTab.java)点击底部按钮时切换Fragment。中间的快捷操作按钮使用的是[自定义dialog](http://git.oschina.net/oschina/osc-android-app/blob/master/osc-android-app/src/net/oschina/app/ui/QuickOptionDialog.java),通过点击时加入动画效果实现。 -2. 一级界面 - * 包括资讯、动弹两个模块,采用[ViewPagerFragment](http://git.oschina.net/oschina/osc-android-app/blob/master/osc-android-app/src/net/oschina/app/viewpagerfragment/NewsViewPagerFragment.java)根据滑动到不同页面显示不同信息。 -3. 详情界面 - * 详情界面包括[博客详情](http://git.oschina.net/oschina/osc-android-app/blob/master/osc-android-app/src/net/oschina/app/fragment/BlogDetailFragment.java),[动弹详情](http://git.oschina.net/oschina/osc-android-app/blob/master/osc-android-app/src/net/oschina/app/fragment/TweetDetailFragment.java),[新闻详情](http://git.oschina.net/oschina/osc-android-app/blob/master/osc-android-app/src/net/oschina/app/fragment/NewsDetailFragment.java),[帖子详情](http://git.oschina.net/oschina/osc-android-app/blob/master/osc-android-app/src/net/oschina/app/fragment/PostDetailFragment.java), [活动详情](http://git.oschina.net/oschina/osc-android-app/blob/master/osc-android-app/src/net/oschina/app/fragment/EventDetailFragment.java)等……是通过在Fragment中的WebView直接loadData()加载一段html数据并显示。 - * 而详情Fragment的显示则是通过一个外部DetailActivity,来根据传入的参数不同来加载不同的Fragment。 -4. 链接跳转 - * 整个应用打开链接的规则都定义在UIHelper.openBrowser()方法中,本方法会根据不同的url去解析,如果是www.oschina.net的链接,则会调用相应的界面去展示;如果是git.oschina.net我们目前会使用手机自带的浏览器打开(之后会改为使用[OscGit客户端](http://git.oschina.net/oschina/git-osc-android-project)打开);如果不是oschina的站内链接,则使用内置浏览器打开。 -5. 侧滑菜单 - * [侧滑菜单](http://git.oschina.net/oschina/osc-android-app/blob/master/osc-android-app/src/net/oschina/app/ui/NavigationDrawerFragment.java)采用系统的DrawerLayout实现。关于很多朋友好奇的左上角箭头,是采用的开源控件[DrawerArrowDrawable](http://git.oschina.net/oschina/osc-android-app/blob/master/osc-android-app/src/net/oschina/app/widget/DrawerArrowDrawable.java)(准确的说不应该是控件而是一个Drawable) +##开源协议 -##依赖包介绍 -1. jar包依赖 - * 网络请求库 **android-async-http** :http://loopj.com/android-async-http/ - * 注解绑定控件 **butterknife** http://jakewharton.github.io/butterknife/ - * 网络图片加载库 **KJFrameForAndroid** http://git.oschina.net/kymjs/KJFrameForAndroid - * XML解析库 **xstream** http://xstream.codehaus.org/ -2. 源码依赖 - * **PhotoView-library** :用于图片预览界面展示 - * **UmengShareLib** :用于分享到第三方平台 + The MIT License (MIT) -##开源协议 - Copyright (C) 2014, The OSChina Open Source Project + Copyright (c) 2016 OSChina.net -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. \ No newline at end of file + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index e540323d6a2a30d1cd9d771716ca9684737cdb62..7822e7f9ca95cf2219e4f491c665221174dca4de 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,34 +1,253 @@ apply plugin: 'com.android.application' -apply plugin: 'newlens' +apply from: '../config/properties-util.gradle' android { - compileSdkVersion 23 - buildToolsVersion '21.1.2' + + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - applicationId "net.oschina.app" - minSdkVersion 15 - targetSdkVersion 23 - versionCode 48 - versionName "2.4.1" + applicationId rootProject.ext.applicationId + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + multiDexEnabled true + buildConfigField("String", "API_SERVER_URL", '"https://www.oschina.net/;http://www.oschina.biz/"') + ndk { + //设置支持的SO库架构 + abiFilters 'armeabi' //, 'arm64-v7a', 'x86' , 'x86_64','arm64-v8a', + } + } + + // rename the apk with the version name + applicationVariants.all { variant -> + variant.outputs.each { output -> + output.outputFile = new File( + output.outputFile.parent + "/${variant.buildType.name}", + "osc-android-${variant.versionName}-${variant.productFlavors[0].name}.apk".toLowerCase()) + } + } + + //signing files settings + signingConfigs { + if (propertyHaveSigningConfigs) { + debug { + storeFile file(propertyStoreFileStr) + storePassword propertyStorePwdStr + keyAlias propertyKeyAliasStr + keyPassword propertyKeyPwdStr + } + + release { + storeFile file(propertyStoreFileStr) + storePassword propertyStorePwdStr + keyAlias propertyKeyAliasStr + keyPassword propertyKeyPwdStr + } + } + } + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + } } // 移除lint检查的error lintOptions { abortOnError false } + + //build type setting + buildTypes { + + debug { + minifyEnabled true + shrinkResources true + zipAlignEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + if (propertyHaveSigningConfigs) + signingConfig signingConfigs.debug + } + + release { + minifyEnabled true + shrinkResources true + zipAlignEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + if (propertyHaveSigningConfigs) + signingConfig signingConfigs.release + } + } + + dexOptions { + javaMaxHeapSize "2g" + } + + //product flavors + productFlavors { + oschina { + manifestPlaceholders = [BaiduMobAd_CHANNEL: "oschina"] + } + xiaomi { + manifestPlaceholders = [BaiduMobAd_CHANNEL: "xiaomi"] + } + wandoujia { + manifestPlaceholders = [BaiduMobAd_CHANNEL: "wandoujia"] + } + baidu { + manifestPlaceholders = [BaiduMobAd_CHANNEL: "baidu"] + } + huawei { + manifestPlaceholders = [BaiduMobAd_CHANNEL: "huawei"] + } + lenovo { + manifestPlaceholders = [BaiduMobAd_CHANNEL: "lenovo"] + } + meizu { + manifestPlaceholders = [BaiduMobAd_CHANNEL: "meizu"] + } + oppo { + manifestPlaceholders = [BaiduMobAd_CHANNEL: "oppo"] + } + vivo { + manifestPlaceholders = [BaiduMobAd_CHANNEL: "vivo"] + } + qihoo {//360版本 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "360"] + } + yingyongbao {//应用宝版本 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "yingyongbao"] + } + pp {//阿里应用(PP) + manifestPlaceholders = [BaiduMobAd_CHANNEL: "pp"] + } + jiuyi {//91助手 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "91"] + } + anzhuo {//安卓 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "anzhuo"] + } + anzhi {//安智市场 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "anzhi"] + } + yingyonghui {//应用汇 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "yingyonghui"] + } + mumayi {//木蚂蚁 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "mumayi"] + } + mm {//MM商城 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "mm"] + } + leshi {//乐视 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "leshi"] + } + jifeng {//机锋 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "jifeng"] + } + samsung {//三星 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "samsung"] + } + sougou {//搜狗手机助手 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "sougou"] + } + youyi {//优亿市场 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "youyi"] + } + nduo {//N多网 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "nduo"] + } + liantongwo {//联通沃商店 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "liantongwo"] + } + jinli {//金立 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "jinli"] + } + pc6 {// pc6.com + manifestPlaceholders = [BaiduMobAd_CHANNEL: "pc6"] + } + smartisan {//锤子应用商店 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "smartisan"] + } + maopao {//冒泡堂 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "maopao"] + } + liqu {//历趣世界 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "liqu"] + } + kuan {//酷安 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "kuan"] + } + tianyi {//天翼 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "tianyi"] + } + ersansiwu {//历趣世界 + manifestPlaceholders = [BaiduMobAd_CHANNEL: "2345"] + } + } +} + +repositories { + flatDir { + dirs 'libs' + } + if (propertyHaveDebugCompile) { + maven { + url propertyDebugCompileUrl + } + } } dependencies { + //noinspection GradleCompatible,GradleDependency + compile "com.android.support:support-v4:$rootProject.ext.supportVersion" + //noinspection GradleDependency + compile "com.android.support:appcompat-v7:$rootProject.ext.supportVersion" + //noinspection GradleDependency + compile "com.android.support:design:$rootProject.ext.supportVersion" + //noinspection GradleDependency + compile "com.android.support:recyclerview-v7:$rootProject.ext.supportVersion" + //noinspection GradleDependency + compile "com.android.support:cardview-v7:$rootProject.ext.supportVersion" compile fileTree(include: ['*.jar'], dir: 'libs') - compile project(':social_sdk_library_project') - compile 'com.android.support:appcompat-v7:23.1.0' - compile 'com.github.chrisbanes.photoview:library:1.2.3' + compile project(':open') + if (propertyHaveDebugCompile) { + compile propertyDebugCompile + } + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.github.chrisbanes.photoview:library:1.2.4' compile 'com.loopj.android:android-async-http:1.4.9' - compile 'com.jakewharton:butterknife:6.1.0' - compile 'org.kymjs.kjframe:kjframe:2.6' - compile 'com.networkbench.newlens.agent.android:nbs.newlens.agent:2.2.7' - compile 'com.google.zxing:core:3.2.0' + //noinspection GradleDependency + compile 'com.jakewharton:butterknife:7.0.1' + compile 'com.google.zxing:core:3.3.0' compile 'com.joanzapata.android:android-iconify:1.0.9' compile 'com.makeramen:roundedimageview:2.1.1' + compile 'pub.devrel:easypermissions:0.3.0' + compile 'com.github.bumptech.glide:glide:3.7.0' + //noinspection GradleDependency + compile 'de.hdodenhof:circleimageview:2.0.0' + compile 'com.google.code.gson:gson:2.8.0' + compile 'com.upyun:upyun-android-sdk:2.0.5' + //noinspection GradleDependency + // compile 'com.umeng.analytics:analytics:latest.integration' + //noinspection GradleDependency + compile 'com.baidu.mobstat:mtj-sdk:latest.integration' + //noinspection GradleDependency + compile 'com.android.support:multidex:1.0.1' + compile 'net.oschina.common:common:0.2.1' + compile 'com.belerweb:pinyin4j:2.5.1' + compile 'com.davemorrissey.labs:subsampling-scale-image-view:3.6.0' + compile 'com.hyman:flowlayout-lib:1.1.2' + compile('com.github.mcxtzhang:SwipeDelMenuLayout:V1.2.5') { + exclude group: 'com.android.support:appcompat-v7', module: 'support-v4' + } + testCompile 'junit:junit:4.12' } + + +apply plugin: 'com.getkeepsafe.dexcount' diff --git a/app/libs/alipaySingle-20170510.jar b/app/libs/alipaySingle-20170510.jar new file mode 100644 index 0000000000000000000000000000000000000000..2a11ea8ac9264cd37a8669cd0a1ef37ae24867ef Binary files /dev/null and b/app/libs/alipaySingle-20170510.jar differ diff --git a/app/src/main/jniLibs/armeabi/libzbar.so b/app/libs/armeabi/libzbar.so similarity index 100% rename from app/src/main/jniLibs/armeabi/libzbar.so rename to app/libs/armeabi/libzbar.so diff --git a/app/libs/baidumapapi_v3_2_0.jar b/app/libs/baidumapapi_v3_2_0.jar deleted file mode 100644 index c60212c7633ee1dac18b0cc3292cee99b9bbb290..0000000000000000000000000000000000000000 Binary files a/app/libs/baidumapapi_v3_2_0.jar and /dev/null differ diff --git a/app/libs/locSDK_3.1.jar b/app/libs/locSDK_3.1.jar deleted file mode 100644 index f503848aadcf698ed8c9fe8a04d8bc4dd5de0804..0000000000000000000000000000000000000000 Binary files a/app/libs/locSDK_3.1.jar and /dev/null differ diff --git a/app/libs/pinyin4j-2.5.0.jar b/app/libs/pinyin4j-2.5.0.jar deleted file mode 100755 index e27c5012fdd9f9c843594651ec83cad98b9c02de..0000000000000000000000000000000000000000 Binary files a/app/libs/pinyin4j-2.5.0.jar and /dev/null differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 41abe698d28002306c29e2b019aa0c1f06d893e4..2a3b434236b1048f0700b51bf294ba1b752fea7b 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -15,3 +15,141 @@ #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} + +##---------------Begin: proguard configuration for Gson ---------- +# Gson uses generic type information stored in a class file when working with fields. Proguard +# removes such information by default, so configure it to keep all of it. +-keepattributes Signature + +# For using GSON @Expose annotation +-keepattributes *Annotation* + +# Gson specific classes +-keep class sun.misc.Unsafe { *; } +#-keep class com.google.gson.stream.** { *; } + +# Application classes that will be serialized/deserialized over Gson +-keep class net.oschina.app.improve.bean.** { *; } +-keep class net.oschina.app.improve..git.bean.** { *; } + +-keepattributes EnclosingMethod + +##---------------End: proguard configuration for Gson ---------- + +-keep class net.oschina.app.** { *; } +-keep class net.oschina.common.** { *; } + + +-keep class butterknife.** { *; } +-dontwarn butterknife.internal.** +-keep class **$$ViewBinder { *; } + +-keepclasseswithmembernames class * { + @butterknife.* ; +} + +-keepclasseswithmembernames class * { + @butterknife.* ; +} + +-dontwarn com.thoughtworks.xstream.** +-keep class com.thoughtworks.xstream.** { *; } + +-dontwarn com.makeramen.roundedimageview.** +-keep class com.makeramen.roundedimageview.RoundedTransformationBuilder + +-dontwarn com.tencent.weibo.sdk.android.** +-keep class com.tencent.weibo.sdk.android.** { *; } + +-dontwarn com.squareup.leakcanary.DisplayLeakService +-keep class com.squareup.leakcanary.DisplayLeakService + +-dontwarn android.widget.** +-keep class android.widget.** {*;} + +-dontwarn android.support.v7.widget.** +-keep class android.support.v7.widget.**{*;} + +-dontshrink +-dontoptimize +-dontwarn com.google.android.maps.** +-dontwarn android.webkit.WebView +-dontwarn com.tencent.weibo.sdk.** + +-keepattributes Exceptions,InnerClasses,Signature +-keepattributes *Annotation* +-keepattributes SourceFile,LineNumberTable + +-keep public interface com.tencent.** +-keep public class javax.** +-keep public class android.webkit.** +-keep public class com.tencent.** {*;} + +#open +-keep class net.oschina.open.**{*;} + +-keep class com.sina.weibo.** {*;} +-keep class com.tencent.mm.sdk.** {*;} +-keep class com.tencent.mm.opensdk.** {*;} +-keep class com.tencent.wxop.** {*;} +-keep class com.tencent.mm.sdk.modelmsg.WXMediaMessage {*;} +-keep class com.tencent.mm.sdk.modelmsg.** implements com.tencent.mm.sdk.modelmsg.WXMediaMessage$IMediaObject {*;} + +# baidu map sdk +-keep class com.baidu.** {*;} +-keep class vi.com.** {*;} +-dontwarn com.baidu.** + +# Glide Start +-keep public class * implements com.bumptech.glide.module.GlideModule +-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { + **[] $VALUES; + public *; +} +# Glide End + +#-dontwarn android.net.SSLCertificateSocketFactory + +# 友盟Start +-keepclassmembers class * { + public (org.json.JSONObject); +} +-keep public class net.oschina.app.R$*{ + public static final int *; +} +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} +-dontwarn com.umeng.** +-keep public interface com.umeng.socialize.** +-keep public interface com.umeng.socialize.sensor.** +-keep public interface com.umeng.scrshot.** +-keep public class com.umeng.socialize.* {*;} +-keep class com.umeng.scrshot.** +-keep class com.umeng.socialize.sensor.** +-keep class pub.devrel.easypermissions.** +# 友盟End + +-dontwarn okio.** +-dontwarn android.net.** +-keep class android.net.SSLCertificateSocketFactory{*;} +-dontwarn com.alipay.sdk.sys.** +-keep class com.ta.utdid2.device.UTDevice{*;} + +-keep class com.baidu.bottom.** { *; } +-keep class com.baidu.kirin.** { *; } +-keep class com.baidu.mobstat.** { *; } + +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +-keepattributes EnclosingMethod +-keep class com.AnywayAds.**{*;} +-keep class com.alipay.android.app.IAlixPay{*;} +-keep class com.alipay.android.app.IAlixPay$Stub{*;} +-keep class com.alipay.android.app.IRemoteServiceCallback{*;} +-keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;} +-keep class com.alipay.sdk.app.PayTask{ public *;} +-keep class com.alipay.sdk.app.AuthTask{ public *;} \ No newline at end of file diff --git a/app/src/androidTest/java/net/oschina/app/ApplicationTest.java b/app/src/androidTest/java/net/oschina/app/OSCApplicationTest.java similarity index 61% rename from app/src/androidTest/java/net/oschina/app/ApplicationTest.java rename to app/src/androidTest/java/net/oschina/app/OSCApplicationTest.java index 5b378ac2dc1f57ad81a454efa7534b4304825e4c..d77f0189ef04de5608350ffe1dcde214a09b0067 100644 --- a/app/src/androidTest/java/net/oschina/app/ApplicationTest.java +++ b/app/src/androidTest/java/net/oschina/app/OSCApplicationTest.java @@ -6,8 +6,14 @@ import android.test.ApplicationTestCase; /** * Testing Fundamentals */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { +public class OSCApplicationTest extends ApplicationTestCase { + + public OSCApplicationTest() { super(Application.class); } + + public void testParseUrl(){ + + } + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2240c7c8d535029f094d51907926d7825e5a57c0..f9c74f7e53530d20f251f47d5c81f16d25ebe35f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,31 +1,76 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + android:theme="@style/App.Theme.Light" + tools:replace="android:allowBackup"> + + + android:theme="@style/App.Theme.Launch"> + + + + + + + + + + + + + + + android:noHistory="true" + android:theme="@style/App.Theme.Tweet.Main"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -96,9 +494,12 @@ + android:name=".improve.tweet.activities.TweetPublishActivity" + android:label="弹一弹" + android:launchMode="singleTask" + android:screenOrientation="portrait" + android:theme="@style/App.Theme.Tweet.Publish" + android:windowSoftInputMode="adjustResize"> @@ -121,127 +522,385 @@ - - - - - + + android:theme="@style/App.Theme.Tweet.Main" /> + + + + + + + + + + + + + + + + + + android:label="注册中心一" + android:launchMode="singleTop" + android:screenOrientation="portrait" + android:theme="@style/Account_Base.App.Theme.NoTitle" + android:windowSoftInputMode="stateHidden|adjustResize" /> + android:name=".improve.account.activity.RegisterStepTwoActivity" + android:configChanges="keyboardHidden|orientation" + android:label="注册中心二" + android:launchMode="singleTop" + android:screenOrientation="portrait" + android:theme="@style/Account_Base.App.Theme.NoTitle" + android:windowSoftInputMode="stateHidden|adjustResize" /> - + android:theme="@style/Account_Base.App.Theme.NoTitle" + android:windowSoftInputMode="stateHidden|adjustResize" /> + + - - + android:name=".improve.user.activities.UserMessageActivity" + android:configChanges="keyboardHidden|orientation" + android:label="消息中心" + android:screenOrientation="portrait" + android:theme="@style/App.Theme.Tweet.Main" /> + + + + - + + + + - + - - - - - - - - - - - - - - - - - + - + + + + + + + + + - - - - - + - - + - - - + android:name=".improve.git.feature.FeatureActivity" + android:label="码云推荐" + android:screenOrientation="portrait" + android:theme="@style/App.Theme.Tweet.Main" /> + + + + - - - - - - + + android:label="代码详情" + android:screenOrientation="sensor" + android:theme="@style/App.Theme.Tweet.Main" /> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/README.HTML b/app/src/main/assets/README.HTML new file mode 100644 index 0000000000000000000000000000000000000000..af4512524ebf4a4224f075e78a2c11c94d1aeb7b --- /dev/null +++ b/app/src/main/assets/README.HTML @@ -0,0 +1,11 @@ + + + + + + + +
+ {content} +
+ \ No newline at end of file diff --git a/app/src/main/assets/css/common_detail.css b/app/src/main/assets/css/common_detail.css new file mode 100644 index 0000000000000000000000000000000000000000..0dc3618d350268e94abe6b37473e2d8ccd090c84 --- /dev/null +++ b/app/src/main/assets/css/common_detail.css @@ -0,0 +1,87 @@ +html, body { + font-family: -apple-system, "Helvetica Neue", Helvetica, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Source Han Sans CN", "Source Han Sans SC", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, "WenQuanYi Zen Hei Sharp", sans-serif; + padding: 0px; + margin: 0px; + overflow-x: hidden; +} + +body { + word-wrap: break-word; + font-size: 16px; + line-height: 165%; + color: #111111; +} + +.body-content{ + box-pack: justify; + text-justify: inter-ideograph; + text-align: justify; +} + +h1{ + color: #010101; + margin-top: 0.2em; + word-break: break-all; + font-size: 20px; +} + +h2{ + color: #010101; + margin-top: 0.2em; + word-break: break-all; + font-size: 18px; +} + + h3, h4, h5, h6 { + line-height: 1.2em; + margin: 0; + margin-top: 0.8em; + margin-bottom: 0.8em; + word-break: break-all; +} + + +p{ + margin: 0; + margin-top: 0.8em; + margin-bottom: 0.8em; + word-break: break-all; +} + +table { + overflow: hidden; + max-width: 100%; + border: 1px solid #CFCDD3; + border-collapse: none; + background: #f8f8f8; + font-size: 0.8em; + margin-right: 1px; +} + +img { + max-width: 100%; + margin: 4px 0; + background: #fefefe; + border: none; + box-shadow: none; +} + +pre { + margin-top: 1px; + margin-bottom: 12px; + font-size: 0.8em; + font-family: monaco, Consolas, 'Liberation Mono', Courier, monospace; + border: none; + border-left: 2px solid #CFCDD3; + background: #f8f8f8; + padding: 8px 6px 6px 6px; + line-height: 140%; + overflow: auto; + word-wrap: normal; + white-space: pre; +} + +a { + text-decoration: none; + color: #24cf5f +} diff --git a/app/src/main/assets/css/common_detail_share.css b/app/src/main/assets/css/common_detail_share.css new file mode 100644 index 0000000000000000000000000000000000000000..b013c110e9a9553653247e90bb542d2f964faaa2 --- /dev/null +++ b/app/src/main/assets/css/common_detail_share.css @@ -0,0 +1,87 @@ +html, body { + font-family: -apple-system, "Helvetica Neue", Helvetica, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Source Han Sans CN", "Source Han Sans SC", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, "WenQuanYi Zen Hei Sharp", sans-serif; + padding: 0px; + margin: 0px; + overflow-x: hidden; +} + +body { + word-wrap: break-word; + font-size: 16px; + line-height: 165%; + color: #111111; +} + +.body-content{ + box-pack: justify; + text-justify: inter-ideograph; + text-align: justify; +} + + +h1{ + color: #010101; + margin-top: 0.2em; + word-break: break-all; + font-size: 20px; +} + +h2{ + color: #010101; + margin-top: 0.2em; + word-break: break-all; + font-size: 18px; +} + + h3, h4, h5, h6 { + line-height: 1.2em; + margin: 0; + margin-top: 0.8em; + margin-bottom: 0.8em; + word-break: break-all; +} + + +p{ + margin: 0; + margin-bottom: 0.8em; + word-break: break-all; +} + +table { + overflow: hidden; + max-width: 100%; + border: 1px solid #CFCDD3; + border-collapse: none; + background: #f8f8f8; + font-size: 0.8em; + margin-right: 1px; +} + +img { + max-width: 100%; + margin: 4px 0; + background: #fefefe; + border: none; + box-shadow: none; +} + +pre { + margin-top: 1px; + margin-bottom: 12px; + font-size: 0.8em; + font-family: monaco, Consolas, 'Liberation Mono', Courier, monospace; + border: none; + border-left: 2px solid #CFCDD3; + background: #f8f8f8; + padding: 8px 6px 6px 6px; + line-height: 140%; + overflow: auto; + word-wrap: break-all; + white-space: pre-wrap; +} + +a { + text-decoration: none; + color: #24cf5f +} diff --git a/app/src/main/assets/css/common_new.css b/app/src/main/assets/css/common_new.css new file mode 100644 index 0000000000000000000000000000000000000000..533bb652873c3e7af0850f647cea821a8b392f4d --- /dev/null +++ b/app/src/main/assets/css/common_new.css @@ -0,0 +1,85 @@ +html, body { + padding: 0px; + margin: 0px; + overflow-x: hidden; +} + +body { + word-wrap: break-word; + font-size: 15px; + letter-spacing: 1px; + line-height: 165%; + color: #111111; +} + +.body-content{ + box-pack: justify; + text-justify: inter-ideograph; + text-align: justify; +} + +h1{ + color: #010101; + margin-top: 0.2em; + word-break: break-all; + font-size: 20px; +} + +h2{ + color: #010101; + margin-top: 0.2em; + word-break: break-all; + font-size: 18px; +} + +h3, h4, h5, h6 { + color: #010101; + margin-top: 0.8em; + word-break: break-all; +} + + +table { + overflow: hidden; + max-width: 100%; + border: 1px solid #CFCDD3; + border-collapse: none; + background: #f8f8f8; + font-size: 0.8em; + margin-right: 1px; +} + +img { + max-width: 100%; + margin: 4px 0; + background: #fefefe; + border: none; + box-shadow: none; +} + +p{ + margin: 0; + margin-top: 0.8em; + margin-bottom: 0.8em; + word-break: break-all; +} + +pre { + margin-top: 1px; + margin-bottom: 1px; + font-size: 0.8em; + font-family: monaco, Consolas, 'Liberation Mono', Courier, monospace; + border: none; + border-left: 2px solid #CFCDD3; + background: #f8f8f8; + padding: 8px 6px 6px 6px; + line-height: 140%; + overflow: auto; + word-wrap: normal; + white-space: pre; +} + +a { + text-decoration: none; + color: #24cf5f +} \ No newline at end of file diff --git a/app/src/main/assets/css/git_project.css b/app/src/main/assets/css/git_project.css new file mode 100644 index 0000000000000000000000000000000000000000..4b487f14be01a5852d4bd09b9e7135074982f356 --- /dev/null +++ b/app/src/main/assets/css/git_project.css @@ -0,0 +1,88 @@ +html,body { + font-family: -apple-system, "Helvetica Neue", Helvetica, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Source Han Sans CN", "Source Han Sans SC", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, "WenQuanYi Zen Hei Sharp", sans-serif; + padding: 0px; + margin: 0px; + overflow-x: hidden; +} + +body { + word-wrap: break-word; + font-size: 16px; + line-height: 140%; + color: #111111; +} + + +h1{ + color: #010101; + margin-top: 0.2em; + word-break: break-all; + font-size: 20px; +} + +h2{ + color: #010101; + margin-top: 0.2em; + word-break: break-all; + font-size: 18px; +} + + h3, h4, h5, h6 { + line-height: 1.2em; + margin: 0; + margin-top: 0.8em; + margin-bottom: 0.8em; + word-break: break-all; +} + + +p{ + margin: 0; + margin-bottom: 0.8em; +} + +ul { + padding-left: 20px; +} + +ol { + padding-left: 25px; +} + +table { + overflow: hidden; + max-width: 100%; + border: 1px solid #CFCDD3; + border-collapse: none; + background: #f8f8f8; + font-size: 0.8em; + margin-right: 1px; +} + +img { + max-width: 100%; + margin: 4px 0; + background: #fefefe; + border: none; + box-shadow: none; +} + +pre { + margin-top: 1px; + margin-bottom: 1px; + font-size: 0.8em; + font-family: monaco, Consolas, 'Liberation Mono', Courier, monospace; + border: none; + border-left: 2px solid #CFCDD3; + background: #f8f8f8; + padding: 8px 6px 6px 6px; + line-height: 140%; + overflow: auto; + word-wrap: normal; + white-space: pre; +} + +a { + text-decoration: none; + color: #24cf5f +} diff --git a/app/src/main/assets/data.db b/app/src/main/assets/data.db new file mode 100644 index 0000000000000000000000000000000000000000..ee8248e0d2a22ad85d24973798e5bb8b2bc2d699 Binary files /dev/null and b/app/src/main/assets/data.db differ diff --git a/app/src/main/assets/fonts/Numans-Regular.otf b/app/src/main/assets/fonts/Numans-Regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..62dfc5c5f007900cd3fc24c54987eabce6a00feb Binary files /dev/null and b/app/src/main/assets/fonts/Numans-Regular.otf differ diff --git a/app/src/main/assets/lib/code.css b/app/src/main/assets/lib/code.css new file mode 100644 index 0000000000000000000000000000000000000000..41b8d09e13608b4f29c057fb4e371ff460b10fb6 --- /dev/null +++ b/app/src/main/assets/lib/code.css @@ -0,0 +1,174 @@ +.CodeMirror { + line-height: 1em; + font-family: monospace; + + /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */ + position: relative; + /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */ + overflow: hidden; +} + +.CodeMirror-scroll { + overflow: auto; + height: 300px; + /* This is needed to prevent an IE[67] bug where the scrolled content + is visible outside of the scrolling box. */ + position: relative; + outline: none; +} + +/* Vertical scrollbar */ +.CodeMirror-scrollbar { + position: absolute; + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; + z-index: 5; +} +.CodeMirror-scrollbar-inner { + /* This needs to have a nonzero width in order for the scrollbar to appear + in Firefox and IE9. */ + width: 1px; +} +.CodeMirror-scrollbar.cm-sb-overlap { + /* Ensure that the scrollbar appears in Lion, and that it overlaps the content + rather than sitting to the right of it. */ + position: absolute; + z-index: 1; + float: none; + right: 0; + min-width: 12px; +} +.CodeMirror-scrollbar.cm-sb-nonoverlap { + min-width: 12px; +} +.CodeMirror-scrollbar.cm-sb-ie7 { + min-width: 18px; +} + +.CodeMirror-gutter { + position: absolute; left: 0; top: 0; + z-index: 10; + background-color: #f7f7f7; + border-right: 1px solid #eee; + min-width: 2em; + height: 100%; +} +.CodeMirror-gutter-text { + color: #aaa; + text-align: right; + padding: .4em .2em .4em .4em; + white-space: pre !important; + cursor: default; +} +.CodeMirror-lines { + padding: .4em; + white-space: pre; + cursor: text; +} + +.CodeMirror pre { + -moz-border-radius: 0; + -webkit-border-radius: 0; + -o-border-radius: 0; + border-radius: 0; + border-width: 0; margin: 0; padding: 0; background: transparent; + font-family: inherit; + font-size: inherit; + padding: 0; margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + overflow: visible; +} + +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} +.CodeMirror-wrap .CodeMirror-scroll { + overflow-x: hidden; +} + +.CodeMirror textarea { + outline: none !important; +} + +.CodeMirror pre.CodeMirror-cursor { + z-index: 10; + position: absolute; + visibility: hidden; + border-left: 1px solid black; + border-right: none; + width: 0; +} +.cm-keymap-fat-cursor pre.CodeMirror-cursor { + width: auto; + border: 0; + background: transparent; + background: rgba(0, 200, 0, .4); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800); +} +/* Kludge to turn off filter in ie9+, which also accepts rgba */ +.cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) { + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {} +.CodeMirror-focused pre.CodeMirror-cursor { + visibility: visible; +} + +div.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; } + +.CodeMirror-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* Default theme */ + +.cm-s-default span.cm-keyword {color: #708;} +.cm-s-default span.cm-atom {color: #219;} +.cm-s-default span.cm-number {color: #164;} +.cm-s-default span.cm-def {color: #00f;} +.cm-s-default span.cm-variable {color: black;} +.cm-s-default span.cm-variable-2 {color: #05a;} +.cm-s-default span.cm-variable-3 {color: #085;} +.cm-s-default span.cm-property {color: black;} +.cm-s-default span.cm-operator {color: black;} +.cm-s-default span.cm-comment {color: #a50;} +.cm-s-default span.cm-string {color: #a11;} +.cm-s-default span.cm-string-2 {color: #f50;} +.cm-s-default span.cm-meta {color: #555;} +.cm-s-default span.cm-error {color: #f00;} +.cm-s-default span.cm-qualifier {color: #555;} +.cm-s-default span.cm-builtin {color: #30a;} +.cm-s-default span.cm-bracket {color: #997;} +.cm-s-default span.cm-tag {color: #170;} +.cm-s-default span.cm-attribute {color: #00c;} +.cm-s-default span.cm-header {color: blue;} +.cm-s-default span.cm-quote {color: #090;} +.cm-s-default span.cm-hr {color: #999;} +.cm-s-default span.cm-link {color: #00c;} + +span.cm-header, span.cm-strong {font-weight: bold;} +span.cm-em {font-style: italic;} +span.cm-emstrong {font-style: italic; font-weight: bold;} +span.cm-link {text-decoration: underline;} + +span.cm-invalidchar {color: #f00;} + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} + +@media print { + + /* Hide the cursor when printing */ + .CodeMirror pre.CodeMirror-cursor { + visibility: hidden; + } + +} diff --git a/app/src/main/assets/lib/code.js b/app/src/main/assets/lib/code.js new file mode 100644 index 0000000000000000000000000000000000000000..e49481b585da959aab21a38adc5e845b14c095ff --- /dev/null +++ b/app/src/main/assets/lib/code.js @@ -0,0 +1,3165 @@ +// CodeMirror version 2.35 +// +// All functions that need access to the editor's state live inside +// the CodeMirror function. Below that, at the bottom of the file, +// some utilities are defined. + +// CodeMirror is the only global var we claim +window.CodeMirror = (function() { + "use strict"; + // This is the function that produces an editor instance. Its + // closure is used to store the editor state. + function CodeMirror(place, givenOptions) { + // Determine effective options based on given values and defaults. + var options = {}, defaults = CodeMirror.defaults; + for (var opt in defaults) + if (defaults.hasOwnProperty(opt)) + options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt]; + + var input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em"); + input.setAttribute("wrap", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); + // Wraps and hides input textarea + var inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); + // The empty scrollbar content, used solely for managing the scrollbar thumb. + var scrollbarInner = elt("div", null, "CodeMirror-scrollbar-inner"); + // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself. + var scrollbar = elt("div", [scrollbarInner], "CodeMirror-scrollbar"); + // DIVs containing the selection and the actual code + var lineDiv = elt("div"), selectionDiv = elt("div", null, null, "position: relative; z-index: -1"); + // Blinky cursor, and element used to ensure cursor fits at the end of a line + var cursor = elt("pre", "\u00a0", "CodeMirror-cursor"), widthForcer = elt("pre", "\u00a0", "CodeMirror-cursor", "visibility: hidden"); + // Used to measure text size + var measure = elt("div", null, null, "position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden;"); + var lineSpace = elt("div", [measure, cursor, widthForcer, selectionDiv, lineDiv], null, "position: relative; z-index: 0"); + var gutterText = elt("div", null, "CodeMirror-gutter-text"), gutter = elt("div", [gutterText], "CodeMirror-gutter"); + // Moved around its parent to cover visible view + var mover = elt("div", [gutter, elt("div", [lineSpace], "CodeMirror-lines")], null, "position: relative"); + // Set to the height of the text, causes scrolling + var sizer = elt("div", [mover], null, "position: relative"); + // Provides scrolling + var scroller = elt("div", [sizer], "CodeMirror-scroll"); + scroller.setAttribute("tabIndex", "-1"); + // The element in which the editor lives. + var wrapper = elt("div", [inputDiv, scrollbar, scroller], "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "")); + if (place.appendChild) place.appendChild(wrapper); else place(wrapper); + + themeChanged(); keyMapChanged(); + // Needed to hide big blue blinking cursor on Mobile Safari + if (ios) input.style.width = "0px"; + if (!webkit) scroller.draggable = true; + lineSpace.style.outline = "none"; + if (options.tabindex != null) input.tabIndex = options.tabindex; + if (options.autofocus) focusInput(); + if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; + // Needed to handle Tab key in KHTML + if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute"; + + // Check for OS X >= 10.7. This has transparent scrollbars, so the + // overlaying of one scrollbar with another won't work. This is a + // temporary hack to simply turn off the overlay scrollbar. See + // issue #727. + if (mac_geLion) { scrollbar.style.zIndex = -2; scrollbar.style.visibility = "hidden"; } + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + else if (ie_lt8) scrollbar.style.minWidth = "18px"; + + // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. + var poll = new Delayed(), highlight = new Delayed(), blinker; + + // mode holds a mode API object. doc is the tree of Line objects, + // frontier is the point up to which the content has been parsed, + // and history the undo history (instance of History constructor). + var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), frontier = 0, focused; + loadMode(); + // The selection. These are always maintained to point at valid + // positions. Inverted is used to remember that the user is + // selecting bottom-to-top. + var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false}; + // Selection-related flags. shiftSelecting obviously tracks + // whether the user is holding shift. + var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, draggingText, + overwrite = false, suppressEdits = false, pasteIncoming = false; + // Variables used by startOperation/endOperation to track what + // happened during the operation. + var updateInput, userSelChange, changes, textChanged, selectionChanged, + gutterDirty, callbacks; + // Current visible range (may be bigger than the view window). + var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0; + // bracketHighlighted is used to remember that a bracket has been + // marked. + var bracketHighlighted; + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + var maxLine = getLine(0), updateMaxLine = false, maxLineChanged = true; + var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll + var goalColumn = null; + + // Initialize the content. + operation(function(){setValue(options.value || ""); updateInput = false;})(); + var history = new History(); + + // Register our event handlers. + connect(scroller, "mousedown", operation(onMouseDown)); + connect(scroller, "dblclick", operation(onDoubleClick)); + connect(lineSpace, "selectstart", e_preventDefault); + // Gecko browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for Gecko. + if (!gecko) connect(scroller, "contextmenu", onContextMenu); + connect(scroller, "scroll", onScrollMain); + connect(scrollbar, "scroll", onScrollBar); + connect(scrollbar, "mousedown", function() {if (focused) setTimeout(focusInput, 0);}); + var resizeHandler = connect(window, "resize", function() { + if (wrapper.parentNode) updateDisplay(true); + else resizeHandler(); + }, true); + connect(input, "keyup", operation(onKeyUp)); + connect(input, "input", fastPoll); + connect(input, "keydown", operation(onKeyDown)); + connect(input, "keypress", operation(onKeyPress)); + connect(input, "focus", onFocus); + connect(input, "blur", onBlur); + + function drag_(e) { + if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; + e_stop(e); + } + if (options.dragDrop) { + connect(scroller, "dragstart", onDragStart); + connect(scroller, "dragenter", drag_); + connect(scroller, "dragover", drag_); + connect(scroller, "drop", operation(onDrop)); + } + connect(scroller, "paste", function(){focusInput(); fastPoll();}); + connect(input, "paste", function(){pasteIncoming = true; fastPoll();}); + connect(input, "cut", operation(function(){ + if (!options.readOnly) replaceSelection(""); + })); + + // Needed to handle Tab key in KHTML + if (khtml) connect(sizer, "mouseup", function() { + if (document.activeElement == input) input.blur(); + focusInput(); + }); + + // IE throws unspecified error in certain cases, when + // trying to access activeElement before onload + var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { } + if (hasFocus || options.autofocus) setTimeout(onFocus, 20); + else onBlur(); + + function isLine(l) {return l >= 0 && l < doc.size;} + // The instance object that we'll return. Mostly calls out to + // local functions in the CodeMirror function. Some do some extra + // range checking and/or clipping. operation is used to wrap the + // call so that changes it makes are tracked, and the display is + // updated afterwards. + var instance = wrapper.CodeMirror = { + getValue: getValue, + setValue: operation(setValue), + getSelection: getSelection, + replaceSelection: operation(replaceSelection), + focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();}, + setOption: function(option, value) { + var oldVal = options[option]; + options[option] = value; + if (option == "mode" || option == "indentUnit") loadMode(); + else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();} + else if (option == "readOnly" && !value) {resetInput(true);} + else if (option == "theme") themeChanged(); + else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)(); + else if (option == "tabSize") updateDisplay(true); + else if (option == "keyMap") keyMapChanged(); + else if (option == "tabindex") input.tabIndex = value; + if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || + option == "theme" || option == "lineNumberFormatter") { + gutterChanged(); + updateDisplay(true); + } + }, + getOption: function(option) {return options[option];}, + getMode: function() {return mode;}, + undo: operation(undo), + redo: operation(redo), + indentLine: operation(function(n, dir) { + if (typeof dir != "string") { + if (dir == null) dir = options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(n)) indentLine(n, dir); + }), + indentSelection: operation(indentSelected), + historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, + clearHistory: function() {history = new History();}, + setHistory: function(histData) { + history = new History(); + history.done = histData.done; + history.undone = histData.undone; + }, + getHistory: function() { + function cp(arr) { + for (var i = 0, nw = [], nwelt; i < arr.length; ++i) { + nw.push(nwelt = []); + for (var j = 0, elt = arr[i]; j < elt.length; ++j) { + var old = [], cur = elt[j]; + nwelt.push({start: cur.start, added: cur.added, old: old}); + for (var k = 0; k < cur.old.length; ++k) old.push(hlText(cur.old[k])); + } + } + return nw; + } + return {done: cp(history.done), undone: cp(history.undone)}; + }, + matchBrackets: operation(function(){matchBrackets(true);}), + getTokenAt: operation(function(pos) { + pos = clipPos(pos); + return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), options.tabSize, pos.ch); + }), + getStateAfter: function(line) { + line = clipLine(line == null ? doc.size - 1: line); + return getStateBefore(line + 1); + }, + cursorCoords: function(start, mode) { + if (start == null) start = sel.inverted; + return this.charCoords(start ? sel.from : sel.to, mode); + }, + charCoords: function(pos, mode) { + pos = clipPos(pos); + if (mode == "local") return localCoords(pos, false); + if (mode == "div") return localCoords(pos, true); + return pageCoords(pos); + }, + coordsChar: function(coords) { + var off = eltOffset(lineSpace); + return coordsChar(coords.x - off.left, coords.y - off.top); + }, + markText: operation(markText), + setBookmark: setBookmark, + findMarksAt: findMarksAt, + setMarker: operation(addGutterMarker), + clearMarker: operation(removeGutterMarker), + setLineClass: operation(setLineClass), + hideLine: operation(function(h) {return setLineHidden(h, true);}), + showLine: operation(function(h) {return setLineHidden(h, false);}), + onDeleteLine: function(line, f) { + if (typeof line == "number") { + if (!isLine(line)) return null; + line = getLine(line); + } + (line.handlers || (line.handlers = [])).push(f); + return line; + }, + lineInfo: lineInfo, + getViewport: function() { return {from: showingFrom, to: showingTo};}, + addWidget: function(pos, node, scroll, vert, horiz) { + pos = localCoords(clipPos(pos)); + var top = pos.yBot, left = pos.x; + node.style.position = "absolute"; + sizer.appendChild(node); + if (vert == "over") top = pos.y; + else if (vert == "near") { + var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()), + hspace = Math.max(sizer.clientWidth, lineSpace.clientWidth) - paddingLeft(); + if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight) + top = pos.y - node.offsetHeight; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = (top + paddingTop()) + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = sizer.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (sizer.clientWidth - node.offsetWidth) / 2; + node.style.left = (left + paddingLeft()) + "px"; + } + if (scroll) + scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + lineCount: function() {return doc.size;}, + clipPos: clipPos, + getCursor: function(start) { + if (start == null) start = sel.inverted; + return copyPos(start ? sel.from : sel.to); + }, + somethingSelected: function() {return !posEq(sel.from, sel.to);}, + setCursor: operation(function(line, ch, user) { + if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user); + else setCursor(line, ch, user); + }), + setSelection: operation(function(from, to, user) { + (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from)); + }), + getLine: function(line) {if (isLine(line)) return getLine(line).text;}, + getLineHandle: function(line) {if (isLine(line)) return getLine(line);}, + setLine: operation(function(line, text) { + if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); + }), + removeLine: operation(function(line) { + if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0})); + }), + replaceRange: operation(replaceRange), + getRange: function(from, to, lineSep) {return getRange(clipPos(from), clipPos(to), lineSep);}, + + triggerOnKeyDown: operation(onKeyDown), + execCommand: function(cmd) {return commands[cmd](instance);}, + // Stuff used by commands, probably not much use to outside code. + moveH: operation(moveH), + deleteH: operation(deleteH), + moveV: operation(moveV), + toggleOverwrite: function() { + if(overwrite){ + overwrite = false; + cursor.className = cursor.className.replace(" CodeMirror-overwrite", ""); + } else { + overwrite = true; + cursor.className += " CodeMirror-overwrite"; + } + }, + + posFromIndex: function(off) { + var lineNo = 0, ch; + doc.iter(0, doc.size, function(line) { + var sz = line.text.length + 1; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos({line: lineNo, ch: ch}); + }, + indexFromPos: function (coords) { + if (coords.line < 0 || coords.ch < 0) return 0; + var index = coords.ch; + doc.iter(0, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + scrollTo: function(x, y) { + if (x != null) scroller.scrollLeft = x; + if (y != null) scrollbar.scrollTop = scroller.scrollTop = y; + updateDisplay([]); + }, + getScrollInfo: function() { + return {x: scroller.scrollLeft, y: scrollbar.scrollTop, + height: scrollbar.scrollHeight, width: scroller.scrollWidth}; + }, + setSize: function(width, height) { + function interpret(val) { + val = String(val); + return /^\d+$/.test(val) ? val + "px" : val; + } + if (width != null) wrapper.style.width = interpret(width); + if (height != null) scroller.style.height = interpret(height); + instance.refresh(); + }, + + operation: function(f){return operation(f)();}, + compoundChange: function(f){return compoundChange(f);}, + refresh: function(){ + updateDisplay(true, null, lastScrollTop); + if (scrollbar.scrollHeight > lastScrollTop) + scrollbar.scrollTop = lastScrollTop; + }, + getInputField: function(){return input;}, + getWrapperElement: function(){return wrapper;}, + getScrollerElement: function(){return scroller;}, + getGutterElement: function(){return gutter;} + }; + + function getLine(n) { return getLineAt(doc, n); } + function updateLineHeight(line, height) { + gutterDirty = true; + var diff = height - line.height; + for (var n = line; n; n = n.parent) n.height += diff; + } + + function lineContent(line, wrapAt) { + if (!line.styles) + line.highlight(mode, line.stateAfter = getStateBefore(lineNo(line)), options.tabSize); + return line.getContent(options.tabSize, wrapAt, options.lineWrapping); + } + + function setValue(code) { + var top = {line: 0, ch: 0}; + updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length}, + splitLines(code), top, top); + updateInput = true; + } + function getValue(lineSep) { + var text = []; + doc.iter(0, doc.size, function(line) { text.push(line.text); }); + return text.join(lineSep || "\n"); + } + + function onScrollBar(e) { + if (scrollbar.scrollTop != lastScrollTop) { + lastScrollTop = scroller.scrollTop = scrollbar.scrollTop; + updateDisplay([]); + } + } + + function onScrollMain(e) { + if (options.fixedGutter && gutter.style.left != scroller.scrollLeft + "px") + gutter.style.left = scroller.scrollLeft + "px"; + if (scroller.scrollTop != lastScrollTop) { + lastScrollTop = scroller.scrollTop; + if (scrollbar.scrollTop != lastScrollTop) + scrollbar.scrollTop = lastScrollTop; + updateDisplay([]); + } + if (options.onScroll) options.onScroll(instance); + } + + function onMouseDown(e) { + setShift(e_prop(e, "shiftKey")); + // Check whether this is a click in a widget + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == sizer && n != mover) return; + + // See if this is a click in the gutter + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == gutterText) { + if (options.onGutterClick) + options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e); + return e_preventDefault(e); + } + + var start = posFromMouse(e); + + switch (e_button(e)) { + case 3: + if (gecko) onContextMenu(e); + return; + case 2: + if (start) setCursor(start.line, start.ch, true); + setTimeout(focusInput, 20); + e_preventDefault(e); + return; + } + // For button 1, if it was clicked inside the editor + // (posFromMouse returning non-null), we have to adjust the + // selection. + if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;} + + if (!focused) onFocus(); + + var now = +new Date, type = "single"; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { + type = "triple"; + e_preventDefault(e); + setTimeout(focusInput, 20); + selectLine(start.line); + } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { + type = "double"; + lastDoubleClick = {time: now, pos: start}; + e_preventDefault(e); + var word = findWordAt(start); + setSelectionUser(word.from, word.to); + } else { lastClick = {time: now, pos: start}; } + + function dragEnd(e2) { + if (webkit) scroller.draggable = false; + draggingText = false; + up(); drop(); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + setCursor(start.line, start.ch, true); + focusInput(); + } + } + var last = start, going; + if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && + !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") { + // Let the drag handler handle this. + if (webkit) scroller.draggable = true; + var up = connect(document, "mouseup", operation(dragEnd), true); + var drop = connect(scroller, "drop", operation(dragEnd), true); + draggingText = true; + // IE's approach to draggable + if (scroller.dragDrop) scroller.dragDrop(); + return; + } + e_preventDefault(e); + if (type == "single") setCursor(start.line, start.ch, true); + + var startstart = sel.from, startend = sel.to; + + function doSelect(cur) { + if (type == "single") { + setSelectionUser(start, cur); + } else if (type == "double") { + var word = findWordAt(cur); + if (posLess(cur, startstart)) setSelectionUser(word.from, startend); + else setSelectionUser(startstart, word.to); + } else if (type == "triple") { + if (posLess(cur, startstart)) setSelectionUser(startend, clipPos({line: cur.line, ch: 0})); + else setSelectionUser(startstart, clipPos({line: cur.line + 1, ch: 0})); + } + } + + function extend(e) { + var cur = posFromMouse(e, true); + if (cur && !posEq(cur, last)) { + if (!focused) onFocus(); + last = cur; + doSelect(cur); + updateInput = false; + var visible = visibleLines(); + if (cur.line >= visible.to || cur.line < visible.from) + going = setTimeout(operation(function(){extend(e);}), 150); + } + } + + function done(e) { + clearTimeout(going); + var cur = posFromMouse(e); + if (cur) doSelect(cur); + e_preventDefault(e); + focusInput(); + updateInput = true; + move(); up(); + } + var move = connect(document, "mousemove", operation(function(e) { + clearTimeout(going); + e_preventDefault(e); + if (!ie && !e_button(e)) done(e); + else extend(e); + }), true); + var up = connect(document, "mouseup", operation(done), true); + } + function onDoubleClick(e) { + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == gutterText) return e_preventDefault(e); + e_preventDefault(e); + } + function onDrop(e) { + if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; + e_preventDefault(e); + var pos = posFromMouse(e, true), files = e.dataTransfer.files; + if (!pos || options.readOnly) return; + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0; + var loadFile = function(file, i) { + var reader = new FileReader; + reader.onload = function() { + text[i] = reader.result; + if (++read == n) { + pos = clipPos(pos); + operation(function() { + var end = replaceRange(text.join(""), pos, pos); + setSelectionUser(pos, end); + })(); + } + }; + reader.readAsText(file); + }; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } else { + // Don't do a replace if the drop happened inside of the selected text. + if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return; + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + compoundChange(function() { + var curFrom = sel.from, curTo = sel.to; + setSelectionUser(pos, pos); + if (draggingText) replaceRange("", curFrom, curTo); + replaceSelection(text); + focusInput(); + }); + } + } + catch(e){} + } + } + function onDragStart(e) { + var txt = getSelection(); + e.dataTransfer.setData("Text", txt); + + // Use dummy image instead of default browsers image. + if (e.dataTransfer.setDragImage) + e.dataTransfer.setDragImage(elt('img'), 0, 0); + } + + function doHandleBinding(bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) return false; + } + var prevShift = shiftSelecting; + try { + if (options.readOnly) suppressEdits = true; + if (dropShift) shiftSelecting = null; + bound(instance); + } catch(e) { + if (e != Pass) throw e; + return false; + } finally { + shiftSelecting = prevShift; + suppressEdits = false; + } + return true; + } + var maybeTransition; + function handleKeyBinding(e) { + // Handle auto keymap transitions + var startMap = getKeyMap(options.keyMap), next = startMap.auto; + clearTimeout(maybeTransition); + if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { + if (getKeyMap(options.keyMap) == startMap) { + options.keyMap = (next.call ? next.call(null, instance) : next); + } + }, 50); + + var name = keyNames[e_prop(e, "keyCode")], handled = false; + var flipCtrlCmd = opera && mac; + if (name == null || e.altGraphKey) return false; + if (e_prop(e, "altKey")) name = "Alt-" + name; + if (e_prop(e, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name; + if (e_prop(e, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name; + + var stopped = false; + function stop() { stopped = true; } + + if (e_prop(e, "shiftKey")) { + handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap, + function(b) {return doHandleBinding(b, true);}, stop) + || lookupKey(name, options.extraKeys, options.keyMap, function(b) { + if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b); + }, stop); + } else { + handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop); + } + if (stopped) handled = false; + if (handled) { + e_preventDefault(e); + restartBlink(); + if (ie) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } + } + return handled; + } + function handleCharBinding(e, ch) { + var handled = lookupKey("'" + ch + "'", options.extraKeys, + options.keyMap, function(b) { return doHandleBinding(b, true); }); + if (handled) { + e_preventDefault(e); + restartBlink(); + } + return handled; + } + + var lastStoppedKey = null; + function onKeyDown(e) { + if (!focused) onFocus(); + if (ie && e.keyCode == 27) { e.returnValue = false; } + if (pollingFast) { if (readInput()) pollingFast = false; } + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + var code = e_prop(e, "keyCode"); + // IE does strange things with escape. + setShift(code == 16 || e_prop(e, "shiftKey")); + // First give onKeyEvent option a chance to handle this. + var handled = handleKeyBinding(e); + if (opera) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey")) + replaceSelection(""); + } + } + function onKeyPress(e) { + if (pollingFast) readInput(); + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); + if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(e)) return; + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) { + if (mode.electricChars.indexOf(ch) > -1) + setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75); + } + if (handleCharBinding(e, ch)) return; + fastPoll(); + } + function onKeyUp(e) { + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + if (e_prop(e, "keyCode") == 16) shiftSelecting = null; + } + + function onFocus() { + if (options.readOnly == "nocursor") return; + if (!focused) { + if (options.onFocus) options.onFocus(instance); + focused = true; + if (scroller.className.search(/\bCodeMirror-focused\b/) == -1) + scroller.className += " CodeMirror-focused"; + } + slowPoll(); + restartBlink(); + } + function onBlur() { + if (focused) { + if (options.onBlur) options.onBlur(instance); + focused = false; + if (bracketHighlighted) + operation(function(){ + if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; } + })(); + scroller.className = scroller.className.replace(" CodeMirror-focused", ""); + } + clearInterval(blinker); + setTimeout(function() {if (!focused) shiftSelecting = null;}, 150); + } + + // Replace the range from from to to by the strings in newText. + // Afterwards, set the selection to selFrom, selTo. + function updateLines(from, to, newText, selFrom, selTo) { + if (suppressEdits) return; + var old = []; + doc.iter(from.line, to.line + 1, function(line) { + old.push(newHL(line.text, line.markedSpans)); + }); + if (history) { + history.addChange(from.line, newText.length, old); + while (history.done.length > options.undoDepth) history.done.shift(); + } + var lines = updateMarkedSpans(hlSpans(old[0]), hlSpans(lst(old)), from.ch, to.ch, newText); + updateLinesNoUndo(from, to, lines, selFrom, selTo); + } + function unredoHelper(from, to) { + if (!from.length) return; + var set = from.pop(), out = []; + for (var i = set.length - 1; i >= 0; i -= 1) { + var change = set[i]; + var replaced = [], end = change.start + change.added; + doc.iter(change.start, end, function(line) { replaced.push(newHL(line.text, line.markedSpans)); }); + out.push({start: change.start, added: change.old.length, old: replaced}); + var pos = {line: change.start + change.old.length - 1, + ch: editEnd(hlText(lst(replaced)), hlText(lst(change.old)))}; + updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, + change.old, pos, pos); + } + updateInput = true; + to.push(out); + } + function undo() {unredoHelper(history.done, history.undone);} + function redo() {unredoHelper(history.undone, history.done);} + + function updateLinesNoUndo(from, to, lines, selFrom, selTo) { + if (suppressEdits) return; + var recomputeMaxLength = false, maxLineLength = maxLine.text.length; + if (!options.lineWrapping) + doc.iter(from.line, to.line + 1, function(line) { + if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;} + }); + if (from.line != to.line || lines.length > 1) gutterDirty = true; + + var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line); + var lastHL = lst(lines); + + // First adjust the line structure + if (from.ch == 0 && to.ch == 0 && hlText(lastHL) == "") { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = [], prevLine = null; + for (var i = 0, e = lines.length - 1; i < e; ++i) + added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); + lastLine.update(lastLine.text, hlSpans(lastHL)); + if (nlines) doc.remove(from.line, nlines, callbacks); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (lines.length == 1) { + firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + firstLine.text.slice(to.ch), hlSpans(lines[0])); + } else { + for (var added = [], i = 1, e = lines.length - 1; i < e; ++i) + added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); + added.push(new Line(hlText(lastHL) + firstLine.text.slice(to.ch), hlSpans(lastHL))); + firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); + doc.insert(from.line + 1, added); + } + } else if (lines.length == 1) { + firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]) + lastLine.text.slice(to.ch), hlSpans(lines[0])); + doc.remove(from.line + 1, nlines, callbacks); + } else { + var added = []; + firstLine.update(firstLine.text.slice(0, from.ch) + hlText(lines[0]), hlSpans(lines[0])); + lastLine.update(hlText(lastHL) + lastLine.text.slice(to.ch), hlSpans(lastHL)); + for (var i = 1, e = lines.length - 1; i < e; ++i) + added.push(new Line(hlText(lines[i]), hlSpans(lines[i]))); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks); + doc.insert(from.line + 1, added); + } + if (options.lineWrapping) { + var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3); + doc.iter(from.line, from.line + lines.length, function(line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (guess != line.height) updateLineHeight(line, guess); + }); + } else { + doc.iter(from.line, from.line + lines.length, function(line) { + var l = line.text; + if (!line.hidden && l.length > maxLineLength) { + maxLine = line; maxLineLength = l.length; maxLineChanged = true; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) updateMaxLine = true; + } + + // Adjust frontier, schedule worker + frontier = Math.min(frontier, from.line); + startWorker(400); + + var lendiff = lines.length - nlines - 1; + // Remember that these lines changed, for updating the display + changes.push({from: from.line, to: to.line + 1, diff: lendiff}); + if (options.onChange) { + // Normalize lines to contain only strings, since that's what + // the change event handler expects + for (var i = 0; i < lines.length; ++i) + if (typeof lines[i] != "string") lines[i] = lines[i].text; + var changeObj = {from: from, to: to, text: lines}; + if (textChanged) { + for (var cur = textChanged; cur.next; cur = cur.next) {} + cur.next = changeObj; + } else textChanged = changeObj; + } + + // Update the selection + function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} + setSelection(clipPos(selFrom), clipPos(selTo), + updateLine(sel.from.line), updateLine(sel.to.line)); + } + + function needsScrollbar() { + var realHeight = doc.height * textHeight() + 2 * paddingTop(); + return realHeight * .99 > scroller.offsetHeight ? realHeight : false; + } + + function updateVerticalScroll(scrollTop) { + var scrollHeight = needsScrollbar(); + scrollbar.style.display = scrollHeight ? "block" : "none"; + if (scrollHeight) { + scrollbarInner.style.height = sizer.style.minHeight = scrollHeight + "px"; + scrollbar.style.height = scroller.clientHeight + "px"; + if (scrollTop != null) { + scrollbar.scrollTop = scroller.scrollTop = scrollTop; + // 'Nudge' the scrollbar to work around a Webkit bug where, + // in some situations, we'd end up with a scrollbar that + // reported its scrollTop (and looked) as expected, but + // *behaved* as if it was still in a previous state (i.e. + // couldn't scroll up, even though it appeared to be at the + // bottom). + if (webkit) setTimeout(function() { + if (scrollbar.scrollTop != scrollTop) return; + scrollbar.scrollTop = scrollTop + (scrollTop ? -1 : 1); + scrollbar.scrollTop = scrollTop; + }, 0); + } + } else { + sizer.style.minHeight = ""; + } + // Position the mover div to align with the current virtual scroll position + mover.style.top = displayOffset * textHeight() + "px"; + } + + function computeMaxLength() { + maxLine = getLine(0); maxLineChanged = true; + var maxLineLength = maxLine.text.length; + doc.iter(1, doc.size, function(line) { + var l = line.text; + if (!line.hidden && l.length > maxLineLength) { + maxLineLength = l.length; maxLine = line; + } + }); + updateMaxLine = false; + } + + function replaceRange(code, from, to) { + from = clipPos(from); + if (!to) to = from; else to = clipPos(to); + code = splitLines(code); + function adjustPos(pos) { + if (posLess(pos, from)) return pos; + if (!posLess(to, pos)) return end; + var line = pos.line + code.length - (to.line - from.line) - 1; + var ch = pos.ch; + if (pos.line == to.line) + ch += lst(code).length - (to.ch - (to.line == from.line ? from.ch : 0)); + return {line: line, ch: ch}; + } + var end; + replaceRange1(code, from, to, function(end1) { + end = end1; + return {from: adjustPos(sel.from), to: adjustPos(sel.to)}; + }); + return end; + } + function replaceSelection(code, collapse) { + replaceRange1(splitLines(code), sel.from, sel.to, function(end) { + if (collapse == "end") return {from: end, to: end}; + else if (collapse == "start") return {from: sel.from, to: sel.from}; + else return {from: sel.from, to: end}; + }); + } + function replaceRange1(code, from, to, computeSel) { + var endch = code.length == 1 ? code[0].length + from.ch : lst(code).length; + var newSel = computeSel({line: from.line + code.length - 1, ch: endch}); + updateLines(from, to, code, newSel.from, newSel.to); + } + + function getRange(from, to, lineSep) { + var l1 = from.line, l2 = to.line; + if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch); + var code = [getLine(l1).text.slice(from.ch)]; + doc.iter(l1 + 1, l2, function(line) { code.push(line.text); }); + code.push(getLine(l2).text.slice(0, to.ch)); + return code.join(lineSep || "\n"); + } + function getSelection(lineSep) { + return getRange(sel.from, sel.to, lineSep); + } + + function slowPoll() { + if (pollingFast) return; + poll.set(options.pollInterval, function() { + readInput(); + if (focused) slowPoll(); + }); + } + function fastPoll() { + var missed = false; + pollingFast = true; + function p() { + var changed = readInput(); + if (!changed && !missed) {missed = true; poll.set(60, p);} + else {pollingFast = false; slowPoll();} + } + poll.set(20, p); + } + + // Previnput is a hack to work with IME. If we reset the textarea + // on every change, that breaks IME. So we look for changes + // compared to the previous content instead. (Modern browsers have + // events that indicate IME taking place, but these are not widely + // supported or compatible enough yet to rely on.) + var prevInput = ""; + function readInput() { + if (!focused || hasSelection(input) || options.readOnly) return false; + var text = input.value; + if (text == prevInput) return false; + if (!nestedOperation) startOperation(); + shiftSelecting = null; + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput[same] == text[same]) ++same; + if (same < prevInput.length) + sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)}; + else if (overwrite && posEq(sel.from, sel.to) && !pasteIncoming) + sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))}; + replaceSelection(text.slice(same), "end"); + if (text.length > 1000) { input.value = prevInput = ""; } + else prevInput = text; + if (!nestedOperation) endOperation(); + pasteIncoming = false; + return true; + } + function resetInput(user) { + if (!posEq(sel.from, sel.to)) { + prevInput = ""; + input.value = getSelection(); + if (focused) selectInput(input); + } else if (user) prevInput = input.value = ""; + } + + function focusInput() { + if (options.readOnly != "nocursor") input.focus(); + } + + function scrollCursorIntoView() { + var coords = calculateCursorCoords(); + scrollIntoView(coords.x, coords.y, coords.x, coords.yBot); + if (!focused) return; + var box = sizer.getBoundingClientRect(), doScroll = null; + if (coords.y + box.top < 0) doScroll = true; + else if (coords.y + box.top + textHeight() > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; + if (doScroll != null) { + var hidden = cursor.style.display == "none"; + if (hidden) { + cursor.style.display = ""; + cursor.style.left = coords.x + "px"; + cursor.style.top = (coords.y - displayOffset) + "px"; + } + cursor.scrollIntoView(doScroll); + if (hidden) cursor.style.display = "none"; + } + } + function calculateCursorCoords() { + var cursor = localCoords(sel.inverted ? sel.from : sel.to); + var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x; + return {x: x, y: cursor.y, yBot: cursor.yBot}; + } + function scrollIntoView(x1, y1, x2, y2) { + var scrollPos = calculateScrollPos(x1, y1, x2, y2); + if (scrollPos.scrollLeft != null) {scroller.scrollLeft = scrollPos.scrollLeft;} + if (scrollPos.scrollTop != null) {scrollbar.scrollTop = scroller.scrollTop = scrollPos.scrollTop;} + } + function calculateScrollPos(x1, y1, x2, y2) { + var pl = paddingLeft(), pt = paddingTop(); + y1 += pt; y2 += pt; x1 += pl; x2 += pl; + var screen = scroller.clientHeight, screentop = scrollbar.scrollTop, result = {}; + var docBottom = needsScrollbar() || Infinity; + var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10; + if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1); + else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen; + + var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; + var gutterw = options.fixedGutter ? gutter.clientWidth : 0; + var atLeft = x1 < gutterw + pl + 10; + if (x1 < screenleft + gutterw || atLeft) { + if (atLeft) x1 = 0; + result.scrollLeft = Math.max(0, x1 - 10 - gutterw); + } else if (x2 > screenw + screenleft - 3) { + result.scrollLeft = x2 + 10 - screenw; + } + return result; + } + + function visibleLines(scrollTop) { + var lh = textHeight(), top = (scrollTop != null ? scrollTop : scrollbar.scrollTop) - paddingTop(); + var fromHeight = Math.max(0, Math.floor(top / lh)); + var toHeight = Math.ceil((top + scroller.clientHeight) / lh); + return {from: lineAtHeight(doc, fromHeight), + to: lineAtHeight(doc, toHeight)}; + } + // Uses a set of changes plus the current scroll position to + // determine which DOM updates have to be made, and makes the + // updates. + function updateDisplay(changes, suppressCallback, scrollTop) { + if (!scroller.clientWidth) { + showingFrom = showingTo = displayOffset = 0; + return; + } + // Compute the new visible window + // If scrollTop is specified, use that to determine which lines + // to render instead of the current scrollbar position. + var visible = visibleLines(scrollTop); + // Bail out if the visible area is already rendered and nothing changed. + if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) { + updateVerticalScroll(scrollTop); + return; + } + var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100); + if (showingFrom < from && from - showingFrom < 20) from = showingFrom; + if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo); + + // Create a range of theoretically intact lines, and punch holes + // in that using the change info. + var intact = changes === true ? [] : + computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes); + // Clip off the parts that won't be visible + var intactLines = 0; + for (var i = 0; i < intact.length; ++i) { + var range = intact[i]; + if (range.from < from) {range.domStart += (from - range.from); range.from = from;} + if (range.to > to) range.to = to; + if (range.from >= range.to) intact.splice(i--, 1); + else intactLines += range.to - range.from; + } + if (intactLines == to - from && from == showingFrom && to == showingTo) { + updateVerticalScroll(scrollTop); + return; + } + intact.sort(function(a, b) {return a.domStart - b.domStart;}); + + var th = textHeight(), gutterDisplay = gutter.style.display; + lineDiv.style.display = "none"; + patchDisplay(from, to, intact); + lineDiv.style.display = gutter.style.display = ""; + + var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th; + // This is just a bogus formula that detects when the editor is + // resized or the font size changes. + if (different) lastSizeC = scroller.clientHeight + th; + if (from != showingFrom || to != showingTo && options.onViewportChange) + setTimeout(function(){ + if (options.onViewportChange) options.onViewportChange(instance, from, to); + }); + showingFrom = from; showingTo = to; + displayOffset = heightAtLine(doc, from); + startWorker(100); + + // Since this is all rather error prone, it is honoured with the + // only assertion in the whole file. + if (lineDiv.childNodes.length != showingTo - showingFrom) + throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) + + " nodes=" + lineDiv.childNodes.length); + + function checkHeights() { + var curNode = lineDiv.firstChild, heightChanged = false; + doc.iter(showingFrom, showingTo, function(line) { + // Work around bizarro IE7 bug where, sometimes, our curNode + // is magically replaced with a new node in the DOM, leaving + // us with a reference to an orphan (nextSibling-less) node. + if (!curNode) return; + if (!line.hidden) { + var height = Math.round(curNode.offsetHeight / th) || 1; + if (line.height != height) { + updateLineHeight(line, height); + gutterDirty = heightChanged = true; + } + } + curNode = curNode.nextSibling; + }); + return heightChanged; + } + + if (options.lineWrapping) checkHeights(); + + gutter.style.display = gutterDisplay; + if (different || gutterDirty) { + // If the gutter grew in size, re-check heights. If those changed, re-draw gutter. + updateGutter() && options.lineWrapping && checkHeights() && updateGutter(); + } + updateVerticalScroll(scrollTop); + updateSelection(); + if (!suppressCallback && options.onUpdate) options.onUpdate(instance); + return true; + } + + function computeIntact(intact, changes) { + for (var i = 0, l = changes.length || 0; i < l; ++i) { + var change = changes[i], intact2 = [], diff = change.diff || 0; + for (var j = 0, l2 = intact.length; j < l2; ++j) { + var range = intact[j]; + if (change.to <= range.from && change.diff) + intact2.push({from: range.from + diff, to: range.to + diff, + domStart: range.domStart}); + else if (change.to <= range.from || change.from >= range.to) + intact2.push(range); + else { + if (change.from > range.from) + intact2.push({from: range.from, to: change.from, domStart: range.domStart}); + if (change.to < range.to) + intact2.push({from: change.to + diff, to: range.to + diff, + domStart: range.domStart + (change.to - range.from)}); + } + } + intact = intact2; + } + return intact; + } + + function patchDisplay(from, to, intact) { + function killNode(node) { + var tmp = node.nextSibling; + node.parentNode.removeChild(node); + return tmp; + } + // The first pass removes the DOM nodes that aren't intact. + if (!intact.length) removeChildren(lineDiv); + else { + var domPos = 0, curNode = lineDiv.firstChild, n; + for (var i = 0; i < intact.length; ++i) { + var cur = intact[i]; + while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;} + for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;} + } + while (curNode) curNode = killNode(curNode); + } + // This pass fills in the lines that actually changed. + var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; + doc.iter(from, to, function(line) { + if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); + if (!nextIntact || nextIntact.from > j) { + if (line.hidden) var lineElement = elt("pre"); + else { + var lineElement = lineContent(line); + if (line.className) lineElement.className = line.className; + // Kludge to make sure the styled element lies behind the selection (by z-index) + if (line.bgClassName) { + var pre = elt("pre", "\u00a0", line.bgClassName, "position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2"); + lineElement = elt("div", [pre, lineElement], null, "position: relative"); + } + } + lineDiv.insertBefore(lineElement, curNode); + } else { + curNode = curNode.nextSibling; + } + ++j; + }); + } + + function updateGutter() { + if (!options.gutter && !options.lineNumbers) return; + var hText = mover.offsetHeight, hEditor = scroller.clientHeight; + gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; + var fragment = document.createDocumentFragment(), i = showingFrom, normalNode; + doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) { + if (line.hidden) { + fragment.appendChild(elt("pre")); + } else { + var marker = line.gutterMarker; + var text = options.lineNumbers ? options.lineNumberFormatter(i + options.firstLineNumber) : null; + if (marker && marker.text) + text = marker.text.replace("%N%", text != null ? text : ""); + else if (text == null) + text = "\u00a0"; + var markerElement = fragment.appendChild(elt("pre", null, marker && marker.style)); + markerElement.innerHTML = text; + for (var j = 1; j < line.height; ++j) { + markerElement.appendChild(elt("br")); + markerElement.appendChild(document.createTextNode("\u00a0")); + } + if (!marker) normalNode = i; + } + ++i; + }); + gutter.style.display = "none"; + removeChildrenAndAdd(gutterText, fragment); + // Make sure scrolling doesn't cause number gutter size to pop + if (normalNode != null && options.lineNumbers) { + var node = gutterText.childNodes[normalNode - showingFrom]; + var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = ""; + while (val.length + pad.length < minwidth) pad += "\u00a0"; + if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild); + } + gutter.style.display = ""; + var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2; + lineSpace.style.marginLeft = gutter.offsetWidth + "px"; + gutterDirty = false; + return resized; + } + function updateSelection() { + var collapsed = posEq(sel.from, sel.to); + var fromPos = localCoords(sel.from, true); + var toPos = collapsed ? fromPos : localCoords(sel.to, true); + var headPos = sel.inverted ? fromPos : toPos, th = textHeight(); + var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv); + inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px"; + inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px"; + if (collapsed) { + cursor.style.top = headPos.y + "px"; + cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px"; + cursor.style.display = ""; + selectionDiv.style.display = "none"; + } else { + var sameLine = fromPos.y == toPos.y, fragment = document.createDocumentFragment(); + var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth; + var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight; + var add = function(left, top, right, height) { + var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px" + : "right: " + right + "px"; + fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + + "px; top: " + top + "px; " + rstyle + "; height: " + height + "px")); + }; + if (sel.from.ch && fromPos.y >= 0) { + var right = sameLine ? clientWidth - toPos.x : 0; + add(fromPos.x, fromPos.y, right, th); + } + var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0)); + var middleHeight = Math.min(toPos.y, clientHeight) - middleStart; + if (middleHeight > 0.2 * th) + add(0, middleStart, 0, middleHeight); + if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th) + add(0, toPos.y, clientWidth - toPos.x, th); + removeChildrenAndAdd(selectionDiv, fragment); + cursor.style.display = "none"; + selectionDiv.style.display = ""; + } + } + + function setShift(val) { + if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from); + else shiftSelecting = null; + } + function setSelectionUser(from, to) { + var sh = shiftSelecting && clipPos(shiftSelecting); + if (sh) { + if (posLess(sh, from)) from = sh; + else if (posLess(to, sh)) to = sh; + } + setSelection(from, to); + userSelChange = true; + } + // Update the selection. Last two args are only used by + // updateLines, since they have to be expressed in the line + // numbers before the update. + function setSelection(from, to, oldFrom, oldTo) { + goalColumn = null; + if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;} + if (posEq(sel.from, from) && posEq(sel.to, to)) return; + if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} + + // Skip over hidden lines. + if (from.line != oldFrom) { + var from1 = skipHidden(from, oldFrom, sel.from.ch); + // If there is no non-hidden line left, force visibility on current line + if (!from1) setLineHidden(from.line, false); + else from = from1; + } + if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch); + + if (posEq(from, to)) sel.inverted = false; + else if (posEq(from, sel.to)) sel.inverted = false; + else if (posEq(to, sel.from)) sel.inverted = true; + + if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) { + var head = sel.inverted ? from : to; + if (head.line != sel.from.line && sel.from.line < doc.size) { + var oldLine = getLine(sel.from.line); + if (/^\s+$/.test(oldLine.text)) + setTimeout(operation(function() { + if (oldLine.parent && /^\s+$/.test(oldLine.text)) { + var no = lineNo(oldLine); + replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length}); + } + }, 10)); + } + } + + sel.from = from; sel.to = to; + selectionChanged = true; + } + function skipHidden(pos, oldLine, oldCh) { + function getNonHidden(dir) { + var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1; + while (lNo != end) { + var line = getLine(lNo); + if (!line.hidden) { + var ch = pos.ch; + if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length; + return {line: lNo, ch: ch}; + } + lNo += dir; + } + } + var line = getLine(pos.line); + var toEnd = pos.ch == line.text.length && pos.ch != oldCh; + if (!line.hidden) return pos; + if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1); + else return getNonHidden(-1) || getNonHidden(1); + } + function setCursor(line, ch, user) { + var pos = clipPos({line: line, ch: ch || 0}); + (user ? setSelectionUser : setSelection)(pos, pos); + } + + function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));} + function clipPos(pos) { + if (pos.line < 0) return {line: 0, ch: 0}; + if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length}; + var ch = pos.ch, linelen = getLine(pos.line).text.length; + if (ch == null || ch > linelen) return {line: pos.line, ch: linelen}; + else if (ch < 0) return {line: pos.line, ch: 0}; + else return pos; + } + + function findPosH(dir, unit) { + var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch; + var lineObj = getLine(line); + function findNextLine() { + for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) { + var lo = getLine(l); + if (!lo.hidden) { line = l; lineObj = lo; return true; } + } + } + function moveOnce(boundToLine) { + if (ch == (dir < 0 ? 0 : lineObj.text.length)) { + if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0; + else return false; + } else ch += dir; + return true; + } + if (unit == "char") moveOnce(); + else if (unit == "column") moveOnce(true); + else if (unit == "word") { + var sawWord = false; + for (;;) { + if (dir < 0) if (!moveOnce()) break; + if (isWordChar(lineObj.text.charAt(ch))) sawWord = true; + else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;} + if (dir > 0) if (!moveOnce()) break; + } + } + return {line: line, ch: ch}; + } + function moveH(dir, unit) { + var pos = dir < 0 ? sel.from : sel.to; + if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit); + setCursor(pos.line, pos.ch, true); + } + function deleteH(dir, unit) { + if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to); + else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to); + else replaceRange("", sel.from, findPosH(dir, unit)); + userSelChange = true; + } + function moveV(dir, unit) { + var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true); + if (goalColumn != null) pos.x = goalColumn; + if (unit == "page") { + var screen = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); + var target = coordsChar(pos.x, pos.y + screen * dir); + } else if (unit == "line") { + var th = textHeight(); + var target = coordsChar(pos.x, pos.y + .5 * th + dir * th); + } + if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y; + setCursor(target.line, target.ch, true); + goalColumn = pos.x; + } + + function findWordAt(pos) { + var line = getLine(pos.line).text; + var start = pos.ch, end = pos.ch; + if (line) { + if (pos.after === false || end == line.length) --start; else ++end; + var startChar = line.charAt(start); + var check = isWordChar(startChar) ? isWordChar : + /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} : + function(ch) {return !/\s/.test(ch) && isWordChar(ch);}; + while (start > 0 && check(line.charAt(start - 1))) --start; + while (end < line.length && check(line.charAt(end))) ++end; + } + return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}}; + } + function selectLine(line) { + setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0})); + } + function indentSelected(mode) { + if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); + var e = sel.to.line - (sel.to.ch ? 0 : 1); + for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode); + } + + function indentLine(n, how) { + if (!how) how = "add"; + if (how == "smart") { + if (!mode.indent) how = "prev"; + else var state = getStateBefore(n); + } + + var line = getLine(n), curSpace = line.indentation(options.tabSize), + curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (how == "smart") { + indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass) how = "prev"; + } + if (how == "prev") { + if (n) indentation = getLine(n-1).indentation(options.tabSize); + else indentation = 0; + } + else if (how == "add") indentation = curSpace + options.indentUnit; + else if (how == "subtract") indentation = curSpace - options.indentUnit; + indentation = Math.max(0, indentation); + var diff = indentation - curSpace; + + var indentString = "", pos = 0; + if (options.indentWithTabs) + for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} + if (pos < indentation) indentString += spaceStr(indentation - pos); + + if (indentString != curSpaceString) + replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length}); + line.stateAfter = null; + } + + function loadMode() { + mode = CodeMirror.getMode(options, options.mode); + doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); + frontier = 0; + startWorker(100); + } + function gutterChanged() { + var visible = options.gutter || options.lineNumbers; + gutter.style.display = visible ? "" : "none"; + if (visible) gutterDirty = true; + else lineDiv.parentNode.style.marginLeft = 0; + } + function wrappingChanged(from, to) { + if (options.lineWrapping) { + wrapper.className += " CodeMirror-wrap"; + var perLine = scroller.clientWidth / charWidth() - 3; + doc.iter(0, doc.size, function(line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (guess != 1) updateLineHeight(line, guess); + }); + lineSpace.style.minWidth = widthForcer.style.left = ""; + } else { + wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); + computeMaxLength(); + doc.iter(0, doc.size, function(line) { + if (line.height != 1 && !line.hidden) updateLineHeight(line, 1); + }); + } + changes.push({from: 0, to: doc.size}); + } + function themeChanged() { + scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") + + options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + } + function keyMapChanged() { + var style = keyMap[options.keyMap].style; + wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + + (style ? " cm-keymap-" + style : ""); + } + + function TextMarker(type, style) { this.lines = []; this.type = type; if (style) this.style = style; } + TextMarker.prototype.clear = operation(function() { + var min, max; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null) min = lineNo(line); + if (span.to != null) max = lineNo(line); + line.markedSpans = removeMarkedSpan(line.markedSpans, span); + } + if (min != null) changes.push({from: min, to: max + 1}); + this.lines.length = 0; + this.explicitlyCleared = true; + }); + TextMarker.prototype.find = function() { + var from, to; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null || span.to != null) { + var found = lineNo(line); + if (span.from != null) from = {line: found, ch: span.from}; + if (span.to != null) to = {line: found, ch: span.to}; + } + } + if (this.type == "bookmark") return from; + return from && {from: from, to: to}; + }; + + function markText(from, to, className, options) { + from = clipPos(from); to = clipPos(to); + var marker = new TextMarker("range", className); + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) + marker[opt] = options[opt]; + var curLine = from.line; + doc.iter(curLine, to.line + 1, function(line) { + var span = {from: curLine == from.line ? from.ch : null, + to: curLine == to.line ? to.ch : null, + marker: marker}; + line.markedSpans = (line.markedSpans || []).concat([span]); + marker.lines.push(line); + ++curLine; + }); + changes.push({from: from.line, to: to.line + 1}); + return marker; + } + + function setBookmark(pos) { + pos = clipPos(pos); + var marker = new TextMarker("bookmark"), line = getLine(pos.line); + history.addChange(pos.line, 1, [newHL(line.text, line.markedSpans)], true); + var span = {from: pos.ch, to: pos.ch, marker: marker}; + line.markedSpans = (line.markedSpans || []).concat([span]); + marker.lines.push(line); + return marker; + } + + function findMarksAt(pos) { + pos = clipPos(pos); + var markers = [], spans = getLine(pos.line).markedSpans; + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + markers.push(span.marker); + } + return markers; + } + + function addGutterMarker(line, text, className) { + if (typeof line == "number") line = getLine(clipLine(line)); + line.gutterMarker = {text: text, style: className}; + gutterDirty = true; + return line; + } + function removeGutterMarker(line) { + if (typeof line == "number") line = getLine(clipLine(line)); + line.gutterMarker = null; + gutterDirty = true; + } + + function changeLine(handle, op) { + var no = handle, line = handle; + if (typeof handle == "number") line = getLine(clipLine(handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no)) changes.push({from: no, to: no + 1}); + else return null; + return line; + } + function setLineClass(handle, className, bgClassName) { + return changeLine(handle, function(line) { + if (line.className != className || line.bgClassName != bgClassName) { + line.className = className; + line.bgClassName = bgClassName; + return true; + } + }); + } + function setLineHidden(handle, hidden) { + return changeLine(handle, function(line, no) { + if (line.hidden != hidden) { + line.hidden = hidden; + if (!options.lineWrapping) { + if (hidden && line.text.length == maxLine.text.length) { + updateMaxLine = true; + } else if (!hidden && line.text.length > maxLine.text.length) { + maxLine = line; updateMaxLine = false; + } + } + updateLineHeight(line, hidden ? 0 : 1); + var fline = sel.from.line, tline = sel.to.line; + if (hidden && (fline == no || tline == no)) { + var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from; + var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to; + // Can't hide the last visible line, we'd have no place to put the cursor + if (!to) return; + setSelection(from, to); + } + return (gutterDirty = true); + } + }); + } + + function lineInfo(line) { + if (typeof line == "number") { + if (!isLine(line)) return null; + var n = line; + line = getLine(line); + if (!line) return null; + } else { + var n = lineNo(line); + if (n == null) return null; + } + var marker = line.gutterMarker; + return {line: n, handle: line, text: line.text, markerText: marker && marker.text, + markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName}; + } + + function measureLine(line, ch) { + if (ch == 0) return {top: 0, left: 0}; + var pre = lineContent(line, ch); + removeChildrenAndAdd(measure, pre); + var anchor = pre.anchor; + var top = anchor.offsetTop, left = anchor.offsetLeft; + // Older IEs report zero offsets for spans directly after a wrap + if (ie && top == 0 && left == 0) { + var backup = elt("span", "x"); + anchor.parentNode.insertBefore(backup, anchor.nextSibling); + top = backup.offsetTop; + } + return {top: top, left: left}; + } + function localCoords(pos, inLineWrap) { + var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0)); + if (pos.ch == 0) x = 0; + else { + var sp = measureLine(getLine(pos.line), pos.ch); + x = sp.left; + if (options.lineWrapping) y += Math.max(0, sp.top); + } + return {x: x, y: y, yBot: y + lh}; + } + // Coords must be lineSpace-local + function coordsChar(x, y) { + var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th); + if (heightPos < 0) return {line: 0, ch: 0}; + var lineNo = lineAtHeight(doc, heightPos); + if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length}; + var lineObj = getLine(lineNo), text = lineObj.text; + var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0; + if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0}; + var wrongLine = false; + function getX(len) { + var sp = measureLine(lineObj, len); + if (tw) { + var off = Math.round(sp.top / th); + wrongLine = off != innerOff; + return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth); + } + return sp.left; + } + var from = 0, fromX = 0, to = text.length, toX; + // Guess a suitable upper bound for our search. + var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw)); + for (;;) { + var estX = getX(estimated); + if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); + else {toX = estX; to = estimated; break;} + } + if (x > toX) return {line: lineNo, ch: to}; + // Try to guess a suitable lower bound as well. + estimated = Math.floor(to * 0.8); estX = getX(estimated); + if (estX < x) {from = estimated; fromX = estX;} + // Do a binary search between these bounds. + for (;;) { + if (to - from <= 1) { + var after = x - fromX < toX - x; + return {line: lineNo, ch: after ? from : to, after: after}; + } + var middle = Math.ceil((from + to) / 2), middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; } + else {from = middle; fromX = middleX;} + } + } + function pageCoords(pos) { + var local = localCoords(pos, true), off = eltOffset(lineSpace); + return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot}; + } + + var cachedHeight, cachedHeightFor, measurePre; + function textHeight() { + if (measurePre == null) { + measurePre = elt("pre"); + for (var i = 0; i < 49; ++i) { + measurePre.appendChild(document.createTextNode("x")); + measurePre.appendChild(elt("br")); + } + measurePre.appendChild(document.createTextNode("x")); + } + var offsetHeight = lineDiv.clientHeight; + if (offsetHeight == cachedHeightFor) return cachedHeight; + cachedHeightFor = offsetHeight; + removeChildrenAndAdd(measure, measurePre.cloneNode(true)); + cachedHeight = measure.firstChild.offsetHeight / 50 || 1; + removeChildren(measure); + return cachedHeight; + } + var cachedWidth, cachedWidthFor = 0; + function charWidth() { + if (scroller.clientWidth == cachedWidthFor) return cachedWidth; + cachedWidthFor = scroller.clientWidth; + var anchor = elt("span", "x"); + var pre = elt("pre", [anchor]); + removeChildrenAndAdd(measure, pre); + return (cachedWidth = anchor.offsetWidth || 10); + } + function paddingTop() {return lineSpace.offsetTop;} + function paddingLeft() {return lineSpace.offsetLeft;} + + function posFromMouse(e, liberal) { + var offW = eltOffset(scroller, true), x, y; + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX; y = e.clientY; } catch (e) { return null; } + // This is a mess of a heuristic to try and determine whether a + // scroll-bar was clicked or not, and to return null if one was + // (and !liberal). + if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight)) + return null; + var offL = eltOffset(lineSpace, true); + return coordsChar(x - offL.left, y - offL.top); + } + var detectingSelectAll; + function onContextMenu(e) { + var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop; + if (!pos || opera) return; // Opera is difficult. + if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) + operation(setCursor)(pos.line, pos.ch); + + var oldCSS = input.style.cssText; + inputDiv.style.position = "absolute"; + input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " + + "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + focusInput(); + resetInput(true); + // Adds "Select all" to context menu in FF + if (posEq(sel.from, sel.to)) input.value = prevInput = " "; + + function rehide() { + inputDiv.style.position = "relative"; + input.style.cssText = oldCSS; + if (ie_lt9) scrollbar.scrollTop = scrollPos; + slowPoll(); + + // Try to detect the user choosing select-all + if (input.selectionStart != null) { + clearTimeout(detectingSelectAll); + var extval = input.value = " " + (posEq(sel.from, sel.to) ? "" : input.value), i = 0; + prevInput = " "; + input.selectionStart = 1; input.selectionEnd = extval.length; + detectingSelectAll = setTimeout(function poll(){ + if (prevInput == " " && input.selectionStart == 0) + operation(commands.selectAll)(instance); + else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); + else resetInput(); + }, 200); + } + } + + if (gecko) { + e_stop(e); + var mouseup = connect(window, "mouseup", function() { + mouseup(); + setTimeout(rehide, 20); + }, true); + } else { + setTimeout(rehide, 50); + } + } + + // Cursor-blinking + function restartBlink() { + clearInterval(blinker); + var on = true; + cursor.style.visibility = ""; + blinker = setInterval(function() { + cursor.style.visibility = (on = !on) ? "" : "hidden"; + }, options.cursorBlinkRate); + } + + var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; + function matchBrackets(autoclear) { + var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1; + var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; + if (!match) return; + var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles; + for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2) + if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;} + + var stack = [line.text.charAt(pos)], re = /[(){}[\]]/; + function scan(line, from, to) { + if (!line.text) return; + var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur; + for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) { + var text = st[i]; + if (st[i+1] != style) {pos += d * text.length; continue;} + for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) { + if (pos >= from && pos < to && re.test(cur = text.charAt(j))) { + var match = matching[cur]; + if (match.charAt(1) == ">" == forward) stack.push(cur); + else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false}; + else if (!stack.length) return {pos: pos, match: true}; + } + } + } + } + for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) { + var line = getLine(i), first = i == head.line; + var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length); + if (found) break; + } + if (!found) found = {pos: null, match: false}; + var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; + var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style), + two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style); + var clear = operation(function(){one.clear(); two && two.clear();}); + if (autoclear) setTimeout(clear, 800); + else bracketHighlighted = clear; + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(n) { + var minindent, minline; + for (var search = n, lim = n - 40; search > lim; --search) { + if (search == 0) return 0; + var line = getLine(search-1); + if (line.stateAfter) return search; + var indented = line.indentation(options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline; + } + function getStateBefore(n) { + var pos = findStartLine(n), state = pos && getLine(pos-1).stateAfter; + if (!state) state = startState(mode); + else state = copyState(mode, state); + doc.iter(pos, n, function(line) { + line.process(mode, state, options.tabSize); + line.stateAfter = (pos == n - 1 || pos % 5 == 0) ? copyState(mode, state) : null; + }); + return state; + } + function highlightWorker() { + if (frontier >= showingTo) return; + var end = +new Date + options.workTime, state = copyState(mode, getStateBefore(frontier)); + var startFrontier = frontier; + doc.iter(frontier, showingTo, function(line) { + if (frontier >= showingFrom) { // Visible + line.highlight(mode, state, options.tabSize); + line.stateAfter = copyState(mode, state); + } else { + line.process(mode, state, options.tabSize); + line.stateAfter = frontier % 5 == 0 ? copyState(mode, state) : null; + } + ++frontier; + if (+new Date > end) { + startWorker(options.workDelay); + return true; + } + }); + if (showingTo > startFrontier && frontier >= showingFrom) + operation(function() {changes.push({from: startFrontier, to: frontier});})(); + } + function startWorker(time) { + if (frontier < showingTo) + highlight.set(time, highlightWorker); + } + + // Operations are used to wrap changes in such a way that each + // change won't have to update the cursor and display (which would + // be awkward, slow, and error-prone), but instead updates are + // batched and then all combined and executed at once. + function startOperation() { + updateInput = userSelChange = textChanged = null; + changes = []; selectionChanged = false; callbacks = []; + } + function endOperation() { + if (updateMaxLine) computeMaxLength(); + if (maxLineChanged && !options.lineWrapping) { + var cursorWidth = widthForcer.offsetWidth, left = measureLine(maxLine, maxLine.text.length).left; + if (!ie_lt8) { + widthForcer.style.left = left + "px"; + lineSpace.style.minWidth = (left + cursorWidth) + "px"; + } + maxLineChanged = false; + } + var newScrollPos, updated; + if (selectionChanged) { + var coords = calculateCursorCoords(); + newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot); + } + if (changes.length || newScrollPos && newScrollPos.scrollTop != null) + updated = updateDisplay(changes, true, newScrollPos && newScrollPos.scrollTop); + if (!updated) { + if (selectionChanged) updateSelection(); + if (gutterDirty) updateGutter(); + } + if (newScrollPos) scrollCursorIntoView(); + if (selectionChanged) restartBlink(); + + if (focused && (updateInput === true || (updateInput !== false && selectionChanged))) + resetInput(userSelChange); + + if (selectionChanged && options.matchBrackets) + setTimeout(operation(function() { + if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} + if (posEq(sel.from, sel.to)) matchBrackets(false); + }), 20); + var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks + if (textChanged && options.onChange && instance) + options.onChange(instance, textChanged); + if (sc && options.onCursorActivity) + options.onCursorActivity(instance); + for (var i = 0; i < cbs.length; ++i) cbs[i](instance); + if (updated && options.onUpdate) options.onUpdate(instance); + } + var nestedOperation = 0; + function operation(f) { + return function() { + if (!nestedOperation++) startOperation(); + try {var result = f.apply(this, arguments);} + finally {if (!--nestedOperation) endOperation();} + return result; + }; + } + + function compoundChange(f) { + history.startCompound(); + try { return f(); } finally { history.endCompound(); } + } + + for (var ext in extensions) + if (extensions.propertyIsEnumerable(ext) && + !instance.propertyIsEnumerable(ext)) + instance[ext] = extensions[ext]; + for (var i = 0; i < initHooks.length; ++i) initHooks[i](instance); + return instance; + } // (end of function CodeMirror) + + // The default configuration options. + CodeMirror.defaults = { + value: "", + mode: null, + theme: "default", + indentUnit: 2, + indentWithTabs: false, + smartIndent: true, + tabSize: 4, + keyMap: "default", + extraKeys: null, + electricChars: true, + autoClearEmptyLines: false, + onKeyEvent: null, + onDragEvent: null, + lineWrapping: false, + lineNumbers: false, + gutter: false, + fixedGutter: false, + firstLineNumber: 1, + readOnly: false, + dragDrop: true, + onChange: null, + onCursorActivity: null, + onViewportChange: null, + onGutterClick: null, + onUpdate: null, + onFocus: null, onBlur: null, onScroll: null, + matchBrackets: false, + cursorBlinkRate: 530, + workTime: 100, + workDelay: 200, + pollInterval: 100, + undoDepth: 40, + tabindex: null, + autofocus: null, + lineNumberFormatter: function(integer) { return integer; } + }; + + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); + var win = /Win/.test(navigator.platform); + + // Known modes, by name and by MIME + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; + CodeMirror.defineMode = function(name, mode) { + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) { + mode.dependencies = []; + for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); + } + modes[name] = mode; + }; + CodeMirror.defineMIME = function(mime, spec) { + mimeModes[mime] = spec; + }; + CodeMirror.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) + spec = mimeModes[spec]; + else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) + return CodeMirror.resolveMode("application/xml"); + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; + }; + CodeMirror.getMode = function(options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) if (exts.hasOwnProperty(prop)) modeObj[prop] = exts[prop]; + } + modeObj.name = spec.name; + return modeObj; + }; + CodeMirror.listModes = function() { + var list = []; + for (var m in modes) + if (modes.propertyIsEnumerable(m)) list.push(m); + return list; + }; + CodeMirror.listMIMEs = function() { + var list = []; + for (var m in mimeModes) + if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]}); + return list; + }; + + var extensions = CodeMirror.extensions = {}; + CodeMirror.defineExtension = function(name, func) { + extensions[name] = func; + }; + + var initHooks = []; + CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + + var modeExtensions = CodeMirror.modeExtensions = {}; + CodeMirror.extendMode = function(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + for (var prop in properties) if (properties.hasOwnProperty(prop)) + exts[prop] = properties[prop]; + }; + + var commands = CodeMirror.commands = { + selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});}, + killLine: function(cm) { + var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); + if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0}); + else cm.replaceRange("", from, sel ? to : {line: from.line}); + }, + deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});}, + undo: function(cm) {cm.undo();}, + redo: function(cm) {cm.redo();}, + goDocStart: function(cm) {cm.setCursor(0, 0, true);}, + goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);}, + goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);}, + goLineStartSmart: function(cm) { + var cur = cm.getCursor(); + var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/)); + cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true); + }, + goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);}, + goLineUp: function(cm) {cm.moveV(-1, "line");}, + goLineDown: function(cm) {cm.moveV(1, "line");}, + goPageUp: function(cm) {cm.moveV(-1, "page");}, + goPageDown: function(cm) {cm.moveV(1, "page");}, + goCharLeft: function(cm) {cm.moveH(-1, "char");}, + goCharRight: function(cm) {cm.moveH(1, "char");}, + goColumnLeft: function(cm) {cm.moveH(-1, "column");}, + goColumnRight: function(cm) {cm.moveH(1, "column");}, + goWordLeft: function(cm) {cm.moveH(-1, "word");}, + goWordRight: function(cm) {cm.moveH(1, "word");}, + delCharLeft: function(cm) {cm.deleteH(-1, "char");}, + delCharRight: function(cm) {cm.deleteH(1, "char");}, + delWordLeft: function(cm) {cm.deleteH(-1, "word");}, + delWordRight: function(cm) {cm.deleteH(1, "word");}, + indentAuto: function(cm) {cm.indentSelection("smart");}, + indentMore: function(cm) {cm.indentSelection("add");}, + indentLess: function(cm) {cm.indentSelection("subtract");}, + insertTab: function(cm) {cm.replaceSelection("\t", "end");}, + defaultTab: function(cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.replaceSelection("\t", "end"); + }, + transposeChars: function(cm) { + var cur = cm.getCursor(), line = cm.getLine(cur.line); + if (cur.ch > 0 && cur.ch < line.length - 1) + cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), + {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1}); + }, + newlineAndIndent: function(cm) { + cm.replaceSelection("\n", "end"); + cm.indentLine(cm.getCursor().line); + }, + toggleOverwrite: function(cm) {cm.toggleOverwrite();} + }; + + var keyMap = CodeMirror.keyMap = {}; + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" + }; + // Note that the save and find-related commands aren't defined by + // default. Unknown commands are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", + "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + fallthrough: "basic" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft", + "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft", + "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", + fallthrough: ["basic", "emacsy"] + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft", + "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" + }; + + function getKeyMap(val) { + if (typeof val == "string") return keyMap[val]; + else return val; + } + function lookupKey(name, extraMap, map, handle, stop) { + function lookup(map) { + map = getKeyMap(map); + var found = map[name]; + if (found === false) { + if (stop) stop(); + return true; + } + if (found != null && handle(found)) return true; + if (map.nofallthrough) { + if (stop) stop(); + return true; + } + var fallthrough = map.fallthrough; + if (fallthrough == null) return false; + if (Object.prototype.toString.call(fallthrough) != "[object Array]") + return lookup(fallthrough); + for (var i = 0, e = fallthrough.length; i < e; ++i) { + if (lookup(fallthrough[i])) return true; + } + return false; + } + if (extraMap && lookup(extraMap)) return true; + return lookup(map); + } + function isModifierKey(event) { + var name = keyNames[e_prop(event, "keyCode")]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + } + CodeMirror.isModifierKey = isModifierKey; + + CodeMirror.fromTextArea = function(textarea, options) { + if (!options) options = {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabindex) + options.tabindex = textarea.tabindex; + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = document.body; + // doc.activeElement occasionally throws on IE + try { hasFocus = document.activeElement; } catch(e) {} + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } + + function save() {textarea.value = instance.getValue();} + if (textarea.form) { + // Deplorable hack to make the submit method do the right thing. + var rmSubmit = connect(textarea.form, "submit", save, true); + if (typeof textarea.form.submit == "function") { + var realSubmit = textarea.form.submit; + textarea.form.submit = function wrappedSubmit() { + save(); + textarea.form.submit = realSubmit; + textarea.form.submit(); + textarea.form.submit = wrappedSubmit; + }; + } + } + + textarea.style.display = "none"; + var instance = CodeMirror(function(node) { + textarea.parentNode.insertBefore(node, textarea.nextSibling); + }, options); + instance.save = save; + instance.getTextArea = function() { return textarea; }; + instance.toTextArea = function() { + save(); + textarea.parentNode.removeChild(instance.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + rmSubmit(); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + return instance; + }; + + var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); + var ie = /MSIE \d/.test(navigator.userAgent); + var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent); + var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); + var quirksMode = ie && document.documentMode == 5; + var webkit = /WebKit\//.test(navigator.userAgent); + var chrome = /Chrome\//.test(navigator.userAgent); + var opera = /Opera\//.test(navigator.userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var khtml = /KHTML\//.test(navigator.userAgent); + var mac_geLion = /Mac OS X 10\D([7-9]|\d\d)\D/.test(navigator.userAgent); + + // Utility functions for working with state. Exported because modes + // sometimes need to do this. + function copyState(mode, state) { + if (state === true) return state; + if (mode.copyState) return mode.copyState(state); + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) val = val.concat([]); + nstate[n] = val; + } + return nstate; + } + CodeMirror.copyState = copyState; + function startState(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true; + } + CodeMirror.startState = startState; + CodeMirror.innerMode = function(mode, state) { + while (mode.innerMode) { + var info = mode.innerMode(state); + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; + }; + + // The character stream used by a mode's parser. + function StringStream(string, tabSize) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + } + StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == 0;}, + peek: function() {return this.string.charAt(this.pos) || undefined;}, + next: function() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++); + }, + eat: function(match) { + var ch = this.string.charAt(this.pos); + if (typeof match == "string") var ok = ch == match; + else var ok = ch && (match.test ? match.test(ch) : match(ch)); + if (ok) {++this.pos; return ch;} + }, + eatWhile: function(match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start; + }, + eatSpace: function() { + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; + return this.pos > start; + }, + skipToEnd: function() {this.pos = this.string.length;}, + skipTo: function(ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true;} + }, + backUp: function(n) {this.pos -= n;}, + column: function() {return countColumn(this.string, this.start, this.tabSize);}, + indentation: function() {return countColumn(this.string, null, this.tabSize);}, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; + if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } else { + var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);} + }; + CodeMirror.StringStream = StringStream; + + function MarkedSpan(from, to, marker) { + this.from = from; this.to = to; this.marker = marker; + } + + function getMarkedSpanFor(spans, marker) { + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) return span; + } + } + + function removeMarkedSpan(spans, span) { + var r; + for (var i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]); + return r; + } + + function markedSpansBefore(old, startCh, endCh) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || marker.type == "bookmark" && span.from == startCh && span.from != endCh) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); + (nw || (nw = [])).push({from: span.from, + to: endsAfter ? null : span.to, + marker: marker}); + } + } + return nw; + } + + function markedSpansAfter(old, endCh) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || marker.type == "bookmark" && span.from == endCh) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); + (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh, + to: span.to == null ? null : span.to - endCh, + marker: marker}); + } + } + return nw; + } + + function updateMarkedSpans(oldFirst, oldLast, startCh, endCh, newText) { + if (!oldFirst && !oldLast) return newText; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh); + var last = markedSpansAfter(oldLast, endCh); + + // Next, merge those two ends + var sameLine = newText.length == 1, offset = lst(newText).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) span.to = startCh; + else if (sameLine) span.to = found.to == null ? null : found.to + offset; + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i = 0; i < last.length; ++i) { + var span = last[i]; + if (span.to != null) span.to += offset; + if (span.from == null) { + var found = getMarkedSpanFor(first, span.marker); + if (!found) { + span.from = offset; + if (sameLine) (first || (first = [])).push(span); + } + } else { + span.from += offset; + if (sameLine) (first || (first = [])).push(span); + } + } + } + + var newMarkers = [newHL(newText[0], first)]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = newText.length - 2, gapMarkers; + if (gap > 0 && first) + for (var i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker}); + for (var i = 0; i < gap; ++i) + newMarkers.push(newHL(newText[i+1], gapMarkers)); + newMarkers.push(newHL(lst(newText), last)); + } + return newMarkers; + } + + // hl stands for history-line, a data structure that can be either a + // string (line without markers) or a {text, markedSpans} object. + function hlText(val) { return typeof val == "string" ? val : val.text; } + function hlSpans(val) { + if (typeof val == "string") return null; + var spans = val.markedSpans, out = null; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } + else if (out) out.push(spans[i]); + } + return !out ? spans : out.length ? out : null; + } + function newHL(text, spans) { return spans ? {text: text, markedSpans: spans} : text; } + + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) return; + for (var i = 0; i < spans.length; ++i) { + var lines = spans[i].marker.lines; + var ix = indexOf(lines, line); + lines.splice(ix, 1); + } + line.markedSpans = null; + } + + function attachMarkedSpans(line, spans) { + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + var marker = spans[i].marker.lines.push(line); + line.markedSpans = spans; + } + + // When measuring the position of the end of a line, different + // browsers require different approaches. If an empty span is added, + // many browsers report bogus offsets. Of those, some (Webkit, + // recent IE) will accept a space without moving the whole span to + // the next line when wrapping it, others work with a zero-width + // space. + var eolSpanContent = " "; + if (gecko || (ie && !ie_lt8)) eolSpanContent = "\u200b"; + else if (opera) eolSpanContent = ""; + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + function Line(text, markedSpans) { + this.text = text; + this.height = 1; + attachMarkedSpans(this, markedSpans); + } + Line.prototype = { + update: function(text, markedSpans) { + this.text = text; + this.stateAfter = this.styles = null; + detachMarkedSpans(this); + attachMarkedSpans(this, markedSpans); + }, + // Run the given mode's parser over a line, update the styles + // array, which contains alternating fragments of text and CSS + // classes. + highlight: function(mode, state, tabSize) { + var stream = new StringStream(this.text, tabSize), st = this.styles || (this.styles = []); + var pos = st.length = 0; + if (this.text == "" && mode.blankLine) mode.blankLine(state); + while (!stream.eol()) { + var style = mode.token(stream, state), substr = stream.current(); + stream.start = stream.pos; + if (pos && st[pos-1] == style) { + st[pos-2] += substr; + } else if (substr) { + st[pos++] = substr; st[pos++] = style; + } + // Give up when line is ridiculously long + if (stream.pos > 5000) { + st[pos++] = this.text.slice(stream.pos); st[pos++] = null; + break; + } + } + }, + process: function(mode, state, tabSize) { + var stream = new StringStream(this.text, tabSize); + if (this.text == "" && mode.blankLine) mode.blankLine(state); + while (!stream.eol() && stream.pos <= 5000) { + mode.token(stream, state); + stream.start = stream.pos; + } + }, + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(mode, state, tabSize, ch) { + var txt = this.text, stream = new StringStream(txt, tabSize); + while (stream.pos < ch && !stream.eol()) { + stream.start = stream.pos; + var style = mode.token(stream, state); + } + return {start: stream.start, + end: stream.pos, + string: stream.current(), + className: style || null, + state: state}; + }, + indentation: function(tabSize) {return countColumn(this.text, null, tabSize);}, + // Produces an HTML fragment for the line, taking selection, + // marking, and highlighting into account. + getContent: function(tabSize, wrapAt, compensateForWrapping) { + var first = true, col = 0, specials = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g; + var pre = elt("pre"); + function span_(html, text, style) { + if (!text) return; + // Work around a bug where, in some compat modes, IE ignores leading spaces + if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1); + first = false; + if (!specials.test(text)) { + col += text.length; + var content = document.createTextNode(text); + } else { + var content = document.createDocumentFragment(), pos = 0; + while (true) { + specials.lastIndex = pos; + var m = specials.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); + col += skipped; + } + if (!m) break; + pos += skipped + 1; + if (m[0] == "\t") { + var tabWidth = tabSize - col % tabSize; + content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + col += tabWidth; + } else { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + m[0].charCodeAt(0).toString(16); + content.appendChild(token); + col += 1; + } + } + } + if (style) html.appendChild(elt("span", [content], style)); + else html.appendChild(content); + } + var span = span_; + if (wrapAt != null) { + var outPos = 0, anchor = pre.anchor = elt("span"); + span = function(html, text, style) { + var l = text.length; + if (wrapAt >= outPos && wrapAt < outPos + l) { + var cut = wrapAt - outPos; + if (cut) { + span_(html, text.slice(0, cut), style); + // See comment at the definition of spanAffectsWrapping + if (compensateForWrapping) { + var view = text.slice(cut - 1, cut + 1); + if (spanAffectsWrapping.test(view)) html.appendChild(elt("wbr")); + else if (!ie_lt8 && /\w\w/.test(view)) html.appendChild(document.createTextNode("\u200d")); + } + } + html.appendChild(anchor); + span_(anchor, opera ? text.slice(cut, cut + 1) : text.slice(cut), style); + if (opera) span_(html, text.slice(cut + 1), style); + wrapAt--; + outPos += l; + } else { + outPos += l; + span_(html, text, style); + if (outPos == wrapAt && outPos == len) { + setTextContent(anchor, eolSpanContent); + html.appendChild(anchor); + } + // Stop outputting HTML when gone sufficiently far beyond measure + else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){}; + } + }; + } + + var st = this.styles, allText = this.text, marked = this.markedSpans; + var len = allText.length; + function styleToClass(style) { + if (!style) return null; + return "cm-" + style.replace(/ +/g, " cm-"); + } + if (!allText && wrapAt == null) { + span(pre, " "); + } else if (!marked || !marked.length) { + for (var i = 0, ch = 0; ch < len; i+=2) { + var str = st[i], style = st[i+1], l = str.length; + if (ch + l > len) str = str.slice(0, len - ch); + ch += l; + span(pre, str, styleToClass(style)); + } + } else { + marked.sort(function(a, b) { return a.from - b.from; }); + var pos = 0, i = 0, text = "", style, sg = 0; + var nextChange = marked[0].from || 0, marks = [], markpos = 0; + var advanceMarks = function() { + var m; + while (markpos < marked.length && + ((m = marked[markpos]).from == pos || m.from == null)) { + if (m.marker.type == "range") marks.push(m); + ++markpos; + } + nextChange = markpos < marked.length ? marked[markpos].from : Infinity; + for (var i = 0; i < marks.length; ++i) { + var to = marks[i].to; + if (to == null) to = Infinity; + if (to == pos) marks.splice(i--, 1); + else nextChange = Math.min(to, nextChange); + } + }; + var m = 0; + while (pos < len) { + if (nextChange == pos) advanceMarks(); + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + var appliedStyle = style; + for (var j = 0; j < marks.length; ++j) { + var mark = marks[j]; + appliedStyle = (appliedStyle ? appliedStyle + " " : "") + mark.marker.style; + if (mark.marker.endStyle && mark.to === Math.min(end, upto)) appliedStyle += " " + mark.marker.endStyle; + if (mark.marker.startStyle && mark.from === pos) appliedStyle += " " + mark.marker.startStyle; + } + span(pre, end > upto ? text.slice(0, upto - pos) : text, appliedStyle); + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} + pos = end; + } + text = st[i++]; style = styleToClass(st[i++]); + } + } + } + return pre; + }, + cleanUp: function() { + this.parent = null; + detachMarkedSpans(this); + } + }; + + // Data structure that holds the sequence of lines. + function LeafChunk(lines) { + this.lines = lines; + this.parent = null; + for (var i = 0, e = lines.length, height = 0; i < e; ++i) { + lines[i].parent = this; + height += lines[i].height; + } + this.height = height; + } + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length; }, + remove: function(at, n, callbacks) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + line.cleanUp(); + if (line.handlers) + for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]); + } + this.lines.splice(at, n); + }, + collapse: function(lines) { + lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); + }, + insertHeight: function(at, lines, height) { + this.height += height; + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; + }, + iterN: function(at, n, op) { + for (var e = at + n; at < e; ++at) + if (op(this.lines[at])) return true; + } + }; + function BranchChunk(children) { + this.children = children; + var size = 0, height = 0; + for (var i = 0, e = children.length; i < e; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this; + } + this.size = size; + this.height = height; + this.parent = null; + } + BranchChunk.prototype = { + chunkSize: function() { return this.size; }, + remove: function(at, n, callbacks) { + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.remove(at, rm, callbacks); + this.height -= oldHeight - child.height; + if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) break; + at = 0; + } else at -= sz; + } + if (this.size - n < 25) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + collapse: function(lines) { + for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines); + }, + insert: function(at, lines) { + var height = 0; + for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; + this.insertHeight(at, lines, height); + }, + insertHeight: function(at, lines, height) { + this.size += lines.length; + this.height += height; + for (var i = 0, e = this.children.length; i < e; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertHeight(at, lines, height); + if (child.lines && child.lines.length > 50) { + while (child.lines.length > 50) { + var spilled = child.lines.splice(child.lines.length - 25, 25); + var newleaf = new LeafChunk(spilled); + child.height -= newleaf.height; + this.children.splice(i + 1, 0, newleaf); + newleaf.parent = this; + } + this.maybeSpill(); + } + break; + } + at -= sz; + } + }, + maybeSpill: function() { + if (this.children.length <= 10) return; + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10); + me.parent.maybeSpill(); + }, + iter: function(from, to, op) { this.iterN(from, to - from, op); }, + iterN: function(at, n, op) { + for (var i = 0, e = this.children.length; i < e; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) return true; + if ((n -= used) == 0) break; + at = 0; + } else at -= sz; + } + } + }; + + function getLineAt(chunk, n) { + while (!chunk.lines) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break; } + n -= sz; + } + } + return chunk.lines[n]; + } + function lineNo(line) { + if (line.parent == null) return null; + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0, e = chunk.children.length; ; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no; + } + function lineAtHeight(chunk, h) { + var n = 0; + outer: do { + for (var i = 0, e = chunk.children.length; i < e; ++i) { + var child = chunk.children[i], ch = child.height; + if (h < ch) { chunk = child; continue outer; } + h -= ch; + n += child.chunkSize(); + } + return n; + } while (!chunk.lines); + for (var i = 0, e = chunk.lines.length; i < e; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) break; + h -= lh; + } + return n + i; + } + function heightAtLine(chunk, n) { + var h = 0; + outer: do { + for (var i = 0, e = chunk.children.length; i < e; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; continue outer; } + n -= sz; + h += child.height; + } + return h; + } while (!chunk.lines); + for (var i = 0; i < n; ++i) h += chunk.lines[i].height; + return h; + } + + // The history object 'chunks' changes that are made close together + // and at almost the same time into bigger undoable units. + function History() { + this.time = 0; + this.done = []; this.undone = []; + this.compound = 0; + this.closed = false; + } + History.prototype = { + addChange: function(start, added, old) { + this.undone.length = 0; + var time = +new Date, cur = lst(this.done), last = cur && lst(cur); + var dtime = time - this.time; + + if (cur && !this.closed && this.compound) { + cur.push({start: start, added: added, old: old}); + } else if (dtime > 400 || !last || this.closed || + last.start > start + old.length || last.start + last.added < start) { + this.done.push([{start: start, added: added, old: old}]); + this.closed = false; + } else { + var startBefore = Math.max(0, last.start - start), + endAfter = Math.max(0, (start + old.length) - (last.start + last.added)); + for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]); + for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]); + if (startBefore) last.start = start; + last.added += added - (old.length - startBefore - endAfter); + } + this.time = time; + }, + startCompound: function() { + if (!this.compound++) this.closed = true; + }, + endCompound: function() { + if (!--this.compound) this.closed = true; + } + }; + + function stopMethod() {e_stop(this);} + // Ensure an event has a stop method. + function addStop(event) { + if (!event.stop) event.stop = stopMethod; + return event; + } + + function e_preventDefault(e) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + } + function e_stopPropagation(e) { + if (e.stopPropagation) e.stopPropagation(); + else e.cancelBubble = true; + } + function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} + CodeMirror.e_stop = e_stop; + CodeMirror.e_preventDefault = e_preventDefault; + CodeMirror.e_stopPropagation = e_stopPropagation; + + function e_target(e) {return e.target || e.srcElement;} + function e_button(e) { + var b = e.which; + if (b == null) { + if (e.button & 1) b = 1; + else if (e.button & 2) b = 3; + else if (e.button & 4) b = 2; + } + if (mac && e.ctrlKey && b == 1) b = 3; + return b; + } + + // Allow 3rd-party code to override event properties by adding an override + // object to an event object. + function e_prop(e, prop) { + var overridden = e.override && e.override.hasOwnProperty(prop); + return overridden ? e.override[prop] : e[prop]; + } + + // Event handler registration. If disconnect is true, it'll return a + // function that unregisters the handler. + function connect(node, type, handler, disconnect) { + if (typeof node.addEventListener == "function") { + node.addEventListener(type, handler, false); + if (disconnect) return function() {node.removeEventListener(type, handler, false);}; + } else { + var wrapHandler = function(event) {handler(event || window.event);}; + node.attachEvent("on" + type, wrapHandler); + if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);}; + } + } + CodeMirror.connect = connect; + + function Delayed() {this.id = null;} + Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; + + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; + + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie_lt9) return false; + var div = elt('div'); + return "draggable" in div || "dragDrop" in div; + }(); + + // Feature-detect whether newlines in textareas are converted to \r\n + var lineSep = function () { + var te = elt("textarea"); + te.value = "foo\nbar"; + if (te.value.indexOf("\r") > -1) return "\r\n"; + return "\n"; + }(); + + // For a reason I have yet to figure out, some browsers disallow + // word wrapping between certain characters *only* if a new inline + // element is started between them. This makes it hard to reliably + // measure the position of things, since that requires inserting an + // extra span. This terribly fragile set of regexps matches the + // character combinations that suffer from this phenomenon on the + // various browsers. + var spanAffectsWrapping = /^$/; // Won't match any two-character string + if (gecko) spanAffectsWrapping = /$'/; + else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; + else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/; + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + function countColumn(string, end, tabSize) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = 0, n = 0; i < end; ++i) { + if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); + else ++n; + } + return n; + } + + function eltOffset(node, screen) { + // Take the parts of bounding client rect that we are interested in so we are able to edit if need be, + // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page) + try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; } + catch(e) { box = {top: 0, left: 0}; } + if (!screen) { + // Get the toplevel scroll, working around browser differences. + if (window.pageYOffset == null) { + var t = document.documentElement || document.body.parentNode; + if (t.scrollTop == null) t = document.body; + box.top += t.scrollTop; box.left += t.scrollLeft; + } else { + box.top += window.pageYOffset; box.left += window.pageXOffset; + } + } + return box; + } + + function eltText(node) { + return node.textContent || node.innerText || node.nodeValue || ""; + } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + spaceStrs.push(lst(spaceStrs) + " "); + return spaceStrs[n]; + } + + function lst(arr) { return arr[arr.length-1]; } + + function selectInput(node) { + if (ios) { // Mobile Safari apparently has a bug where select() is broken. + node.selectionStart = 0; + node.selectionEnd = node.value.length; + } else node.select(); + } + + // Operations on {line, ch} objects. + function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} + function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} + function copyPos(x) {return {line: x.line, ch: x.ch};} + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) e.className = className; + if (style) e.style.cssText = style; + if (typeof content == "string") setTextContent(e, content); + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); + return e; + } + function removeChildren(e) { + e.innerHTML = ""; + return e; + } + function removeChildrenAndAdd(parent, e) { + removeChildren(parent).appendChild(e); + } + function setTextContent(e, str) { + if (ie_lt9) { + e.innerHTML = ""; + e.appendChild(document.createTextNode(str)); + } else e.textContent = str; + } + + // Used to position the cursor after an undo/redo by finding the + // last edited character. + function editEnd(from, to) { + if (!to) return 0; + if (!from) return to.length; + for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j) + if (from.charAt(i) != to.charAt(j)) break; + return j + 1; + } + + function indexOf(collection, elt) { + if (collection.indexOf) return collection.indexOf(elt); + for (var i = 0, e = collection.length; i < e; ++i) + if (collection[i] == elt) return i; + return -1; + } + function isWordChar(ch) { + return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase() || /[\u4E00-\u9FA5]/.test(ch); + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) nl = string.length; + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } + } + return result; + } : function(string){return string.split(/\r\n?|\n/);}; + CodeMirror.splitLines = splitLines; + + var hasSelection = window.getSelection ? function(te) { + try { return te.selectionStart != te.selectionEnd; } + catch(e) { return false; } + } : function(te) { + try {var range = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range || range.parentElement() != te) return false; + return range.compareEndPoints("StartToEnd", range) != 0; + }; + + CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; + }); + CodeMirror.defineMIME("text/plain", "null"); + + var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", + 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", + 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; + CodeMirror.keyNames = keyNames; + (function() { + // Number keys + for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i); + // Alphabetic keys + for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); + // Function keys + for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; + })(); + + CodeMirror.version = "2.35"; + + return CodeMirror; +})(); diff --git a/app/src/main/assets/lib/util/load.js b/app/src/main/assets/lib/util/load.js new file mode 100644 index 0000000000000000000000000000000000000000..c7a980cc6019b13dd52884218bc517d88c1adc5a --- /dev/null +++ b/app/src/main/assets/lib/util/load.js @@ -0,0 +1,51 @@ +(function() { + if (!CodeMirror.modeURL) CodeMirror.modeURL = "../mode/%N/%N.js"; + + var loading = {}; + function splitCallback(cont, n) { + var countDown = n; + return function() { if (--countDown == 0) cont(); }; + } + function ensureDeps(mode, cont) { + var deps = CodeMirror.modes[mode].dependencies; + if (!deps) return cont(); + var missing = []; + for (var i = 0; i < deps.length; ++i) { + if (!CodeMirror.modes.hasOwnProperty(deps[i])) + missing.push(deps[i]); + } + if (!missing.length) return cont(); + var split = splitCallback(cont, missing.length); + for (var i = 0; i < missing.length; ++i) + CodeMirror.requireMode(missing[i], split); + } + + CodeMirror.requireMode = function(mode, cont) { + if (typeof mode != "string") mode = mode.name; + if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont); + if (loading.hasOwnProperty(mode)) return loading[mode].push(cont); + + var script = document.createElement("script"); + script.src = CodeMirror.modeURL.replace(/%N/g, mode); + var others = document.getElementsByTagName("script")[0]; + others.parentNode.insertBefore(script, others); + var list = loading[mode] = [cont]; + var count = 0, poll = setInterval(function() { + if (++count > 100) return clearInterval(poll); + if (CodeMirror.modes.hasOwnProperty(mode)) { + clearInterval(poll); + loading[mode] = null; + ensureDeps(mode, function() { + for (var i = 0; i < list.length; ++i) list[i](); + }); + } + }, 200); + }; + + CodeMirror.autoLoadMode = function(instance, mode) { + if (!CodeMirror.modes.hasOwnProperty(mode)) + CodeMirror.requireMode(mode, function() { + instance.setOption("mode", instance.getOption("mode")); + }); + }; +}()); diff --git a/app/src/main/assets/lib/util/overlay.js b/app/src/main/assets/lib/util/overlay.js new file mode 100644 index 0000000000000000000000000000000000000000..fba38987bbf8a357881bfd84758abac24efdaca2 --- /dev/null +++ b/app/src/main/assets/lib/util/overlay.js @@ -0,0 +1,59 @@ +// Utility function that allows modes to be combined. The mode given +// as the base argument takes care of most of the normal mode +// functionality, but a second (typically simple) mode is used, which +// can override the style of text. Both modes get to parse all of the +// text, but when both assign a non-null style to a piece of code, the +// overlay wins, unless the combine argument was true, in which case +// the styles are combined. + +// overlayParser is the old, deprecated name +CodeMirror.overlayMode = CodeMirror.overlayParser = function(base, overlay, combine) { + return { + startState: function() { + return { + base: CodeMirror.startState(base), + overlay: CodeMirror.startState(overlay), + basePos: 0, baseCur: null, + overlayPos: 0, overlayCur: null + }; + }, + copyState: function(state) { + return { + base: CodeMirror.copyState(base, state.base), + overlay: CodeMirror.copyState(overlay, state.overlay), + basePos: state.basePos, baseCur: null, + overlayPos: state.overlayPos, overlayCur: null + }; + }, + + token: function(stream, state) { + if (stream.start == state.basePos) { + state.baseCur = base.token(stream, state.base); + state.basePos = stream.pos; + } + if (stream.start == state.overlayPos) { + stream.pos = stream.start; + state.overlayCur = overlay.token(stream, state.overlay); + state.overlayPos = stream.pos; + } + stream.pos = Math.min(state.basePos, state.overlayPos); + if (stream.eol()) state.basePos = state.overlayPos = 0; + + if (state.overlayCur == null) return state.baseCur; + if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur; + else return state.overlayCur; + }, + + indent: base.indent && function(state, textAfter) { + return base.indent(state.base, textAfter); + }, + electricChars: base.electricChars, + + innerMode: function(state) { return {state: state.base, mode: base}; }, + + blankLine: function(state) { + if (base.blankLine) base.blankLine(state.base); + if (overlay.blankLine) overlay.blankLine(state.overlay); + } + }; +}; diff --git a/app/src/main/assets/mode/clike/clike.js b/app/src/main/assets/mode/clike/clike.js new file mode 100644 index 0000000000000000000000000000000000000000..69668a44d3169d5717a2c0ad26f578e7f815cadb --- /dev/null +++ b/app/src/main/assets/mode/clike/clike.js @@ -0,0 +1,285 @@ +CodeMirror.defineMode("clike", function(config, parserConfig) { + var indentUnit = config.indentUnit, + keywords = parserConfig.keywords || {}, + builtin = parserConfig.builtin || {}, + blockKeywords = parserConfig.blockKeywords || {}, + atoms = parserConfig.atoms || {}, + hooks = parserConfig.hooks || {}, + multiLineStrings = parserConfig.multiLineStrings; + var isOperatorChar = /[+\-*&%=<>!?|\/]/; + + var curPunc; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (hooks[ch]) { + var result = hooks[ch](stream, state); + if (result !== false) return result; + } + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + curPunc = ch; + return null; + } + if (/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + return "number"; + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "operator"; + } + stream.eatWhile(/[\w\$_]/); + var cur = stream.current(); + if (keywords.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "keyword"; + } + if (builtin.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "builtin"; + } + if (atoms.propertyIsEnumerable(cur)) return "atom"; + return "variable"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = null; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = null; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + function pushContext(state, col, type) { + return state.context = new Context(state.indented, col, type, null, state.context); + } + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment" || style == "meta") return style; + if (ctx.align == null) ctx.align = true; + + if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); + else if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "}") { + while (ctx.type == "statement") ctx = popContext(state); + if (ctx.type == "}") ctx = popContext(state); + while (ctx.type == "statement") ctx = popContext(state); + } + else if (curPunc == ctx.type) popContext(state); + else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) + pushContext(state, stream.column(), "statement"); + state.startOfLine = false; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize == tokenComment) return CodeMirror.Pass; + if (state.tokenize != tokenBase && state.tokenize != null) return 0; + var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); + if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; + var closing = firstChar == ctx.type; + if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit); + else if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indented + (closing ? 0 : indentUnit); + }, + + electricChars: "{}" + }; +}); + +(function() { + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + var cKeywords = "auto if break int case long char register continue return default short do sizeof " + + "double static else struct entry switch extern typedef float union for unsigned " + + "goto while enum void const signed volatile"; + + function cppHook(stream, state) { + if (!state.startOfLine) return false; + stream.skipToEnd(); + return "meta"; + } + + // C#-style strings where "" escapes a quote. + function tokenAtString(stream, state) { + var next; + while ((next = stream.next()) != null) { + if (next == '"' && !stream.eat('"')) { + state.tokenize = null; + break; + } + } + return "string"; + } + + function mimes(ms, mode) { + for (var i = 0; i < ms.length; ++i) CodeMirror.defineMIME(ms[i], mode); + } + + mimes(["text/x-csrc", "text/x-c", "text/x-chdr"], { + name: "clike", + keywords: words(cKeywords), + blockKeywords: words("case do else for if switch while struct"), + atoms: words("null"), + hooks: {"#": cppHook} + }); + mimes(["text/x-c++src", "text/x-c++hdr"], { + name: "clike", + keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " + + "static_cast typeid catch operator template typename class friend private " + + "this using const_cast inline public throw virtual delete mutable protected " + + "wchar_t"), + blockKeywords: words("catch class do else finally for if struct switch try while"), + atoms: words("true false null"), + hooks: {"#": cppHook} + }); + CodeMirror.defineMIME("text/x-java", { + name: "clike", + keywords: words("abstract assert boolean break byte case catch char class const continue default " + + "do double else enum extends final finally float for goto if implements import " + + "instanceof int interface long native new package private protected public " + + "return short static strictfp super switch synchronized this throw throws transient " + + "try void volatile while"), + blockKeywords: words("catch class do else finally for if switch try while"), + atoms: words("true false null"), + hooks: { + "@": function(stream, state) { + stream.eatWhile(/[\w\$_]/); + return "meta"; + } + } + }); + CodeMirror.defineMIME("text/x-csharp", { + name: "clike", + keywords: words("abstract as base break case catch checked class const continue" + + " default delegate do else enum event explicit extern finally fixed for" + + " foreach goto if implicit in interface internal is lock namespace new" + + " operator out override params private protected public readonly ref return sealed" + + " sizeof stackalloc static struct switch this throw try typeof unchecked" + + " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + + " global group into join let orderby partial remove select set value var yield"), + blockKeywords: words("catch class do else finally for foreach if struct switch try while"), + builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" + + " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" + + " UInt64 bool byte char decimal double short int long object" + + " sbyte float string ushort uint ulong"), + atoms: words("true false null"), + hooks: { + "@": function(stream, state) { + if (stream.eat('"')) { + state.tokenize = tokenAtString; + return tokenAtString(stream, state); + } + stream.eatWhile(/[\w\$_]/); + return "meta"; + } + } + }); + CodeMirror.defineMIME("text/x-scala", { + name: "clike", + keywords: words( + + /* scala */ + "abstract case catch class def do else extends false final finally for forSome if " + + "implicit import lazy match new null object override package private protected return " + + "sealed super this throw trait try trye type val var while with yield _ : = => <- <: " + + "<% >: # @ " + + + /* package scala */ + "assert assume require print println printf readLine readBoolean readByte readShort " + + "readChar readInt readLong readFloat readDouble " + + + "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " + + "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " + + "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " + + "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " + + "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector :: #:: " + + + /* package java.lang */ + "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + + "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + + "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + + "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void" + + + ), + blockKeywords: words("catch class do else finally for forSome if match switch try while"), + atoms: words("true false null"), + hooks: { + "@": function(stream, state) { + stream.eatWhile(/[\w\$_]/); + return "meta"; + } + } + }); +}()); diff --git a/app/src/main/assets/mode/clojure/clojure.js b/app/src/main/assets/mode/clojure/clojure.js new file mode 100644 index 0000000000000000000000000000000000000000..84f6073fd52f7a408970d8739a60534e3b7fb89e --- /dev/null +++ b/app/src/main/assets/mode/clojure/clojure.js @@ -0,0 +1,206 @@ +/** + * Author: Hans Engel + * Branched from CodeMirror's Scheme mode (by Koh Zi Han, based on implementation by Koh Zi Chun) + */ +CodeMirror.defineMode("clojure", function (config, mode) { + var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", TAG = "tag", + ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD = "keyword"; + var INDENT_WORD_SKIP = 2, KEYWORDS_SKIP = 1; + + function makeKeywords(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var atoms = makeKeywords("true false nil"); + + var keywords = makeKeywords( + "defn defn- def def- defonce defmulti defmethod defmacro defstruct deftype defprotocol defrecord defproject deftest slice defalias defhinted defmacro- defn-memo defnk defnk defonce- defunbound defunbound- defvar defvar- let letfn do case cond condp for loop recur when when-not when-let when-first if if-let if-not . .. -> ->> doto and or dosync doseq dotimes dorun doall load import unimport ns in-ns refer try catch finally throw with-open with-local-vars binding gen-class gen-and-load-class gen-and-save-class handler-case handle"); + + var builtins = makeKeywords( + "* *1 *2 *3 *agent* *allow-unresolved-vars* *assert *clojure-version* *command-line-args* *compile-files* *compile-path* *e *err* *file* *flush-on-newline* *in* *macro-meta* *math-context* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* *print-readably* *read-eval* *source-path* *use-context-classloader* *warn-on-reflection* + - / < <= = == > >= accessor aclone agent agent-errors aget alength alias all-ns alter alter-meta! alter-var-root amap ancestors and apply areduce array-map aset aset-boolean aset-byte aset-char aset-double aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in associative? atom await await-for await1 bases bean bigdec bigint binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array booleans bound-fn bound-fn* butlast byte byte-array bytes case cast char char-array char-escape-string char-name-string char? chars chunk chunk-append chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class class? clear-agent-errors clojure-version coll? comment commute comp comparator compare compare-and-set! compile complement concat cond condp conj conj! cons constantly construct-proxy contains? count counted? create-ns create-struct cycle dec decimal? declare definline defmacro defmethod defmulti defn defn- defonce defstruct delay delay? deliver deref derive descendants destructure disj disj! dissoc dissoc! distinct distinct? doall doc dorun doseq dosync dotimes doto double double-array doubles drop drop-last drop-while empty empty? ensure enumeration-seq eval even? every? extend extend-protocol extend-type extends? extenders false? ffirst file-seq filter find find-doc find-ns find-var first float float-array float? floats flush fn fn? fnext for force format future future-call future-cancel future-cancelled? future-done? future? gen-class gen-interface gensym get get-in get-method get-proxy-class get-thread-bindings get-validator hash hash-map hash-set identical? identity if-let if-not ifn? import in-ns inc init-proxy instance? int int-array integer? interleave intern interpose into into-array ints io! isa? iterate iterator-seq juxt key keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* list? load load-file load-reader load-string loaded-libs locking long long-array longs loop macroexpand macroexpand-1 make-array make-hierarchy map map? mapcat max max-key memfn memoize merge merge-with meta method-sig methods min min-key mod name namespace neg? newline next nfirst nil? nnext not not-any? not-empty not-every? not= ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias ns-unmap nth nthnext num number? odd? or parents partial partition pcalls peek persistent! pmap pop pop! pop-thread-bindings pos? pr pr-str prefer-method prefers primitives-classnames print print-ctor print-doc print-dup print-method print-namespace-doc print-simple print-special-doc print-str printf println println-str prn prn-str promise proxy proxy-call-with-super proxy-mappings proxy-name proxy-super push-thread-bindings pvalues quot rand rand-int range ratio? rational? rationalize re-find re-groups re-matcher re-matches re-pattern re-seq read read-line read-string reify reduce ref ref-history-count ref-max-history ref-min-history ref-set refer refer-clojure release-pending-sends rem remove remove-method remove-ns repeat repeatedly replace replicate require reset! reset-meta! resolve rest resultset-seq reverse reversible? rseq rsubseq satisfies? second select-keys send send-off seq seq? seque sequence sequential? set set-validator! set? short short-array shorts shutdown-agents slurp some sort sort-by sorted-map sorted-map-by sorted-set sorted-set-by sorted? special-form-anchor special-symbol? split-at split-with str stream? string? struct struct-map subs subseq subvec supers swap! symbol symbol? sync syntax-symbol-anchor take take-last take-nth take-while test the-ns time to-array to-array-2d trampoline transient tree-seq true? type unchecked-add unchecked-dec unchecked-divide unchecked-inc unchecked-multiply unchecked-negate unchecked-remainder unchecked-subtract underive unquote unquote-splicing update-in update-proxy use val vals var-get var-set var? vary-meta vec vector vector? when when-first when-let when-not while with-bindings with-bindings* with-in-str with-loading-context with-local-vars with-meta with-open with-out-str with-precision xml-seq"); + + var indentKeys = makeKeywords( + // Built-ins + "ns fn def defn defmethod bound-fn if if-not case condp when while when-not when-first do future comment doto locking proxy with-open with-precision reify deftype defrecord defprotocol extend extend-protocol extend-type try catch " + + + // Binding forms + "let letfn binding loop for doseq dotimes when-let if-let " + + + // Data structures + "defstruct struct-map assoc " + + + // clojure.test + "testing deftest " + + + // contrib + "handler-case handle dotrace deftrace"); + + var tests = { + digit: /\d/, + digit_or_colon: /[\d:]/, + hex: /[0-9a-f]/i, + sign: /[+-]/, + exponent: /e/i, + keyword_char: /[^\s\(\[\;\)\]]/, + basic: /[\w\$_\-]/, + lang_keyword: /[\w*+!\-_?:\/]/ + }; + + function stateStack(indent, type, prev) { // represents a state stack object + this.indent = indent; + this.type = type; + this.prev = prev; + } + + function pushStack(state, indent, type) { + state.indentStack = new stateStack(indent, type, state.indentStack); + } + + function popStack(state) { + state.indentStack = state.indentStack.prev; + } + + function isNumber(ch, stream){ + // hex + if ( ch === '0' && stream.eat(/x/i) ) { + stream.eatWhile(tests.hex); + return true; + } + + // leading sign + if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) { + stream.eat(tests.sign); + ch = stream.next(); + } + + if ( tests.digit.test(ch) ) { + stream.eat(ch); + stream.eatWhile(tests.digit); + + if ( '.' == stream.peek() ) { + stream.eat('.'); + stream.eatWhile(tests.digit); + } + + if ( stream.eat(tests.exponent) ) { + stream.eat(tests.sign); + stream.eatWhile(tests.digit); + } + + return true; + } + + return false; + } + + return { + startState: function () { + return { + indentStack: null, + indentation: 0, + mode: false + }; + }, + + token: function (stream, state) { + if (state.indentStack == null && stream.sol()) { + // update indentation, but only if indentStack is empty + state.indentation = stream.indentation(); + } + + // skip spaces + if (stream.eatSpace()) { + return null; + } + var returnType = null; + + switch(state.mode){ + case "string": // multi-line string parsing mode + var next, escaped = false; + while ((next = stream.next()) != null) { + if (next == "\"" && !escaped) { + + state.mode = false; + break; + } + escaped = !escaped && next == "\\"; + } + returnType = STRING; // continue on in string mode + break; + default: // default parsing mode + var ch = stream.next(); + + if (ch == "\"") { + state.mode = "string"; + returnType = STRING; + } else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) { + returnType = ATOM; + } else if (ch == ";") { // comment + stream.skipToEnd(); // rest of the line is a comment + returnType = COMMENT; + } else if (isNumber(ch,stream)){ + returnType = NUMBER; + } else if (ch == "(" || ch == "[") { + var keyWord = '', indentTemp = stream.column(), letter; + /** + Either + (indent-word .. + (non-indent-word .. + (;something else, bracket, etc. + */ + + if (ch == "(") while ((letter = stream.eat(tests.keyword_char)) != null) { + keyWord += letter; + } + + if (keyWord.length > 0 && (indentKeys.propertyIsEnumerable(keyWord) || + /^(?:def|with)/.test(keyWord))) { // indent-word + pushStack(state, indentTemp + INDENT_WORD_SKIP, ch); + } else { // non-indent word + // we continue eating the spaces + stream.eatSpace(); + if (stream.eol() || stream.peek() == ";") { + // nothing significant after + // we restart indentation 1 space after + pushStack(state, indentTemp + 1, ch); + } else { + pushStack(state, indentTemp + stream.current().length, ch); // else we match + } + } + stream.backUp(stream.current().length - 1); // undo all the eating + + returnType = BRACKET; + } else if (ch == ")" || ch == "]") { + returnType = BRACKET; + if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : "[")) { + popStack(state); + } + } else if ( ch == ":" ) { + stream.eatWhile(tests.lang_keyword); + return ATOM; + } else { + stream.eatWhile(tests.basic); + + if (keywords && keywords.propertyIsEnumerable(stream.current())) { + returnType = KEYWORD; + } else if (builtins && builtins.propertyIsEnumerable(stream.current())) { + returnType = BUILTIN; + } else if (atoms && atoms.propertyIsEnumerable(stream.current())) { + returnType = ATOM; + } else returnType = null; + } + } + + return returnType; + }, + + indent: function (state, textAfter) { + if (state.indentStack == null) return state.indentation; + return state.indentStack.indent; + } + }; +}); + +CodeMirror.defineMIME("text/x-clojure", "clojure"); diff --git a/app/src/main/assets/mode/coffeescript/LICENSE b/app/src/main/assets/mode/coffeescript/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..977e284e0f2bc785c3ec89030af88796b99ed1af --- /dev/null +++ b/app/src/main/assets/mode/coffeescript/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2011 Jeff Pickhardt +Modified from the Python CodeMirror mode, Copyright (c) 2010 Timothy Farrell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/app/src/main/assets/mode/coffeescript/coffeescript.js b/app/src/main/assets/mode/coffeescript/coffeescript.js new file mode 100644 index 0000000000000000000000000000000000000000..e9b97f14eb68a0c5210b8269a0523eff6da331ca --- /dev/null +++ b/app/src/main/assets/mode/coffeescript/coffeescript.js @@ -0,0 +1,346 @@ +/** + * Link to the project's GitHub page: + * https://github.com/pickhardt/coffeescript-codemirror-mode + */ +CodeMirror.defineMode('coffeescript', function(conf) { + var ERRORCLASS = 'error'; + + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b"); + } + + var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\?]"); + var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\},:`=;\\.]'); + var doubleOperators = new RegExp("^((\->)|(\=>)|(\\+\\+)|(\\+\\=)|(\\-\\-)|(\\-\\=)|(\\*\\*)|(\\*\\=)|(\\/\\/)|(\\/\\=)|(==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//))"); + var doubleDelimiters = new RegExp("^((\\.\\.)|(\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); + var tripleDelimiters = new RegExp("^((\\.\\.\\.)|(//=)|(>>=)|(<<=)|(\\*\\*=))"); + var identifiers = new RegExp("^[_A-Za-z$][_A-Za-z$0-9]*"); + var properties = new RegExp("^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*"); + + var wordOperators = wordRegexp(['and', 'or', 'not', + 'is', 'isnt', 'in', + 'instanceof', 'typeof']); + var indentKeywords = ['for', 'while', 'loop', 'if', 'unless', 'else', + 'switch', 'try', 'catch', 'finally', 'class']; + var commonKeywords = ['break', 'by', 'continue', 'debugger', 'delete', + 'do', 'in', 'of', 'new', 'return', 'then', + 'this', 'throw', 'when', 'until']; + + var keywords = wordRegexp(indentKeywords.concat(commonKeywords)); + + indentKeywords = wordRegexp(indentKeywords); + + + var stringPrefixes = new RegExp("^('{3}|\"{3}|['\"])"); + var regexPrefixes = new RegExp("^(/{3}|/)"); + var commonConstants = ['Infinity', 'NaN', 'undefined', 'null', 'true', 'false', 'on', 'off', 'yes', 'no']; + var constants = wordRegexp(commonConstants); + + // Tokenizers + function tokenBase(stream, state) { + // Handle scope changes + if (stream.sol()) { + var scopeOffset = state.scopes[0].offset; + if (stream.eatSpace()) { + var lineOffset = stream.indentation(); + if (lineOffset > scopeOffset) { + return 'indent'; + } else if (lineOffset < scopeOffset) { + return 'dedent'; + } + return null; + } else { + if (scopeOffset > 0) { + dedent(stream, state); + } + } + } + if (stream.eatSpace()) { + return null; + } + + var ch = stream.peek(); + + // Handle docco title comment (single line) + if (stream.match("####")) { + stream.skipToEnd(); + return 'comment'; + } + + // Handle multi line comments + if (stream.match("###")) { + state.tokenize = longComment; + return state.tokenize(stream, state); + } + + // Single line comment + if (ch === '#') { + stream.skipToEnd(); + return 'comment'; + } + + // Handle number literals + if (stream.match(/^-?[0-9\.]/, false)) { + var floatLiteral = false; + // Floats + if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) { + floatLiteral = true; + } + if (stream.match(/^-?\d+\.\d*/)) { + floatLiteral = true; + } + if (stream.match(/^-?\.\d+/)) { + floatLiteral = true; + } + + if (floatLiteral) { + // prevent from getting extra . on 1.. + if (stream.peek() == "."){ + stream.backUp(1); + } + return 'number'; + } + // Integers + var intLiteral = false; + // Hex + if (stream.match(/^-?0x[0-9a-f]+/i)) { + intLiteral = true; + } + // Decimal + if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) { + intLiteral = true; + } + // Zero by itself with no other piece of number. + if (stream.match(/^-?0(?![\dx])/i)) { + intLiteral = true; + } + if (intLiteral) { + return 'number'; + } + } + + // Handle strings + if (stream.match(stringPrefixes)) { + state.tokenize = tokenFactory(stream.current(), 'string'); + return state.tokenize(stream, state); + } + // Handle regex literals + if (stream.match(regexPrefixes)) { + if (stream.current() != '/' || stream.match(/^.*\//, false)) { // prevent highlight of division + state.tokenize = tokenFactory(stream.current(), 'string-2'); + return state.tokenize(stream, state); + } else { + stream.backUp(1); + } + } + + // Handle operators and delimiters + if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { + return 'punctuation'; + } + if (stream.match(doubleOperators) + || stream.match(singleOperators) + || stream.match(wordOperators)) { + return 'operator'; + } + if (stream.match(singleDelimiters)) { + return 'punctuation'; + } + + if (stream.match(constants)) { + return 'atom'; + } + + if (stream.match(keywords)) { + return 'keyword'; + } + + if (stream.match(identifiers)) { + return 'variable'; + } + + if (stream.match(properties)) { + return 'property'; + } + + // Handle non-detected items + stream.next(); + return ERRORCLASS; + } + + function tokenFactory(delimiter, outclass) { + var singleline = delimiter.length == 1; + return function tokenString(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^'"\/\\]/); + if (stream.eat('\\')) { + stream.next(); + if (singleline && stream.eol()) { + return outclass; + } + } else if (stream.match(delimiter)) { + state.tokenize = tokenBase; + return outclass; + } else { + stream.eat(/['"\/]/); + } + } + if (singleline) { + if (conf.mode.singleLineStringErrors) { + outclass = ERRORCLASS; + } else { + state.tokenize = tokenBase; + } + } + return outclass; + }; + } + + function longComment(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^#]/); + if (stream.match("###")) { + state.tokenize = tokenBase; + break; + } + stream.eatWhile("#"); + } + return "comment"; + } + + function indent(stream, state, type) { + type = type || 'coffee'; + var indentUnit = 0; + if (type === 'coffee') { + for (var i = 0; i < state.scopes.length; i++) { + if (state.scopes[i].type === 'coffee') { + indentUnit = state.scopes[i].offset + conf.indentUnit; + break; + } + } + } else { + indentUnit = stream.column() + stream.current().length; + } + state.scopes.unshift({ + offset: indentUnit, + type: type + }); + } + + function dedent(stream, state) { + if (state.scopes.length == 1) return; + if (state.scopes[0].type === 'coffee') { + var _indent = stream.indentation(); + var _indent_index = -1; + for (var i = 0; i < state.scopes.length; ++i) { + if (_indent === state.scopes[i].offset) { + _indent_index = i; + break; + } + } + if (_indent_index === -1) { + return true; + } + while (state.scopes[0].offset !== _indent) { + state.scopes.shift(); + } + return false; + } else { + state.scopes.shift(); + return false; + } + } + + function tokenLexer(stream, state) { + var style = state.tokenize(stream, state); + var current = stream.current(); + + // Handle '.' connected identifiers + if (current === '.') { + style = state.tokenize(stream, state); + current = stream.current(); + if (style === 'variable') { + return 'variable'; + } else { + return ERRORCLASS; + } + } + + // Handle scope changes. + if (current === 'return') { + state.dedent += 1; + } + if (((current === '->' || current === '=>') && + !state.lambda && + state.scopes[0].type == 'coffee' && + stream.peek() === '') + || style === 'indent') { + indent(stream, state); + } + var delimiter_index = '[({'.indexOf(current); + if (delimiter_index !== -1) { + indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1)); + } + if (indentKeywords.exec(current)){ + indent(stream, state); + } + if (current == 'then'){ + dedent(stream, state); + } + + + if (style === 'dedent') { + if (dedent(stream, state)) { + return ERRORCLASS; + } + } + delimiter_index = '])}'.indexOf(current); + if (delimiter_index !== -1) { + if (dedent(stream, state)) { + return ERRORCLASS; + } + } + if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'coffee') { + if (state.scopes.length > 1) state.scopes.shift(); + state.dedent -= 1; + } + + return style; + } + + var external = { + startState: function(basecolumn) { + return { + tokenize: tokenBase, + scopes: [{offset:basecolumn || 0, type:'coffee'}], + lastToken: null, + lambda: false, + dedent: 0 + }; + }, + + token: function(stream, state) { + var style = tokenLexer(stream, state); + + state.lastToken = {style:style, content: stream.current()}; + + if (stream.eol() && stream.lambda) { + state.lambda = false; + } + + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase) { + return 0; + } + + return state.scopes[0].offset; + } + + }; + return external; +}); + +CodeMirror.defineMIME('text/x-coffeescript', 'coffeescript'); diff --git a/app/src/main/assets/mode/commonlisp/commonlisp.js b/app/src/main/assets/mode/commonlisp/commonlisp.js new file mode 100644 index 0000000000000000000000000000000000000000..4fb4bdf9bdf568d0f6eb4ea2c6c03bb8c2638b74 --- /dev/null +++ b/app/src/main/assets/mode/commonlisp/commonlisp.js @@ -0,0 +1,101 @@ +CodeMirror.defineMode("commonlisp", function (config) { + var assumeBody = /^with|^def|^do|^prog|case$|^cond$|bind$|when$|unless$/; + var numLiteral = /^(?:[+\-]?(?:\d+|\d*\.\d+)(?:[efd][+\-]?\d+)?|[+\-]?\d+(?:\/[+\-]?\d+)?|#b[+\-]?[01]+|#o[+\-]?[0-7]+|#x[+\-]?[\da-f]+)/; + var symbol = /[^\s'`,@()\[\]";]/; + var type; + + function readSym(stream) { + var ch; + while (ch = stream.next()) { + if (ch == "\\") stream.next(); + else if (!symbol.test(ch)) { stream.backUp(1); break; } + } + return stream.current(); + } + + function base(stream, state) { + if (stream.eatSpace()) {type = "ws"; return null;} + if (stream.match(numLiteral)) return "number"; + var ch = stream.next(); + if (ch == "\\") ch = stream.next(); + + if (ch == '"') return (state.tokenize = inString)(stream, state); + else if (ch == "(") { type = "open"; return "bracket"; } + else if (ch == ")" || ch == "]") { type = "close"; return "bracket"; } + else if (ch == ";") { stream.skipToEnd(); type = "ws"; return "comment"; } + else if (/['`,@]/.test(ch)) return null; + else if (ch == "|") { + if (stream.skipTo("|")) { stream.next(); return "symbol"; } + else { stream.skipToEnd(); return "error"; } + } else if (ch == "#") { + var ch = stream.next(); + if (ch == "[") { type = "open"; return "bracket"; } + else if (/[+\-=\.']/.test(ch)) return null; + else if (/\d/.test(ch) && stream.match(/^\d*#/)) return null; + else if (ch == "|") return (state.tokenize = inComment)(stream, state); + else if (ch == ":") { readSym(stream); return "meta"; } + else return "error"; + } else { + var name = readSym(stream); + if (name == ".") return null; + type = "symbol"; + if (name == "nil" || name == "t") return "atom"; + if (name.charAt(0) == ":") return "keyword"; + if (name.charAt(0) == "&") return "variable-2"; + return "variable"; + } + } + + function inString(stream, state) { + var escaped = false, next; + while (next = stream.next()) { + if (next == '"' && !escaped) { state.tokenize = base; break; } + escaped = !escaped && next == "\\"; + } + return "string"; + } + + function inComment(stream, state) { + var next, last; + while (next = stream.next()) { + if (next == "#" && last == "|") { state.tokenize = base; break; } + last = next; + } + type = "ws"; + return "comment"; + } + + return { + startState: function () { + return {ctx: {prev: null, start: 0, indentTo: 0}, tokenize: base}; + }, + + token: function (stream, state) { + if (stream.sol() && typeof state.ctx.indentTo != "number") + state.ctx.indentTo = state.ctx.start + 1; + + type = null; + var style = state.tokenize(stream, state); + if (type != "ws") { + if (state.ctx.indentTo == null) { + if (type == "symbol" && assumeBody.test(stream.current())) + state.ctx.indentTo = state.ctx.start + config.indentUnit; + else + state.ctx.indentTo = "next"; + } else if (state.ctx.indentTo == "next") { + state.ctx.indentTo = stream.column(); + } + } + if (type == "open") state.ctx = {prev: state.ctx, start: stream.column(), indentTo: null}; + else if (type == "close") state.ctx = state.ctx.prev || state.ctx; + return style; + }, + + indent: function (state, textAfter) { + var i = state.ctx.indentTo; + return typeof i == "number" ? i : state.ctx.start + 1; + } + }; +}); + +CodeMirror.defineMIME("text/x-common-lisp", "commonlisp"); diff --git a/app/src/main/assets/mode/css/css.js b/app/src/main/assets/mode/css/css.js new file mode 100644 index 0000000000000000000000000000000000000000..87d5d7401ee8687d685b6d4abc084cc34a6e40e3 --- /dev/null +++ b/app/src/main/assets/mode/css/css.js @@ -0,0 +1,448 @@ +CodeMirror.defineMode("css", function(config) { + var indentUnit = config.indentUnit, type; + + var atMediaTypes = keySet([ + "all", "aural", "braille", "handheld", "print", "projection", "screen", + "tty", "tv", "embossed" + ]); + + var atMediaFeatures = keySet([ + "width", "min-width", "max-width", "height", "min-height", "max-height", + "device-width", "min-device-width", "max-device-width", "device-height", + "min-device-height", "max-device-height", "aspect-ratio", + "min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio", + "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color", + "max-color", "color-index", "min-color-index", "max-color-index", + "monochrome", "min-monochrome", "max-monochrome", "resolution", + "min-resolution", "max-resolution", "scan", "grid" + ]); + + var propertyKeywords = keySet([ + "align-content", "align-items", "align-self", "alignment-adjust", + "alignment-baseline", "anchor-point", "animation", "animation-delay", + "animation-direction", "animation-duration", "animation-iteration-count", + "animation-name", "animation-play-state", "animation-timing-function", + "appearance", "azimuth", "backface-visibility", "background", + "background-attachment", "background-clip", "background-color", + "background-image", "background-origin", "background-position", + "background-repeat", "background-size", "baseline-shift", "binding", + "bleed", "bookmark-label", "bookmark-level", "bookmark-state", + "bookmark-target", "border", "border-bottom", "border-bottom-color", + "border-bottom-left-radius", "border-bottom-right-radius", + "border-bottom-style", "border-bottom-width", "border-collapse", + "border-color", "border-image", "border-image-outset", + "border-image-repeat", "border-image-slice", "border-image-source", + "border-image-width", "border-left", "border-left-color", + "border-left-style", "border-left-width", "border-radius", "border-right", + "border-right-color", "border-right-style", "border-right-width", + "border-spacing", "border-style", "border-top", "border-top-color", + "border-top-left-radius", "border-top-right-radius", "border-top-style", + "border-top-width", "border-width", "bottom", "box-decoration-break", + "box-shadow", "box-sizing", "break-after", "break-before", "break-inside", + "caption-side", "clear", "clip", "color", "color-profile", "column-count", + "column-fill", "column-gap", "column-rule", "column-rule-color", + "column-rule-style", "column-rule-width", "column-span", "column-width", + "columns", "content", "counter-increment", "counter-reset", "crop", "cue", + "cue-after", "cue-before", "cursor", "direction", "display", + "dominant-baseline", "drop-initial-after-adjust", + "drop-initial-after-align", "drop-initial-before-adjust", + "drop-initial-before-align", "drop-initial-size", "drop-initial-value", + "elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis", + "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", + "float", "float-offset", "font", "font-feature-settings", "font-family", + "font-kerning", "font-language-override", "font-size", "font-size-adjust", + "font-stretch", "font-style", "font-synthesis", "font-variant", + "font-variant-alternates", "font-variant-caps", "font-variant-east-asian", + "font-variant-ligatures", "font-variant-numeric", "font-variant-position", + "font-weight", "grid-cell", "grid-column", "grid-column-align", + "grid-column-sizing", "grid-column-span", "grid-columns", "grid-flow", + "grid-row", "grid-row-align", "grid-row-sizing", "grid-row-span", + "grid-rows", "grid-template", "hanging-punctuation", "height", "hyphens", + "icon", "image-orientation", "image-rendering", "image-resolution", + "inline-box-align", "justify-content", "left", "letter-spacing", + "line-break", "line-height", "line-stacking", "line-stacking-ruby", + "line-stacking-shift", "line-stacking-strategy", "list-style", + "list-style-image", "list-style-position", "list-style-type", "margin", + "margin-bottom", "margin-left", "margin-right", "margin-top", + "marker-offset", "marks", "marquee-direction", "marquee-loop", + "marquee-play-count", "marquee-speed", "marquee-style", "max-height", + "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index", + "nav-left", "nav-right", "nav-up", "opacity", "order", "orphans", "outline", + "outline-color", "outline-offset", "outline-style", "outline-width", + "overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y", + "padding", "padding-bottom", "padding-left", "padding-right", "padding-top", + "page", "page-break-after", "page-break-before", "page-break-inside", + "page-policy", "pause", "pause-after", "pause-before", "perspective", + "perspective-origin", "pitch", "pitch-range", "play-during", "position", + "presentation-level", "punctuation-trim", "quotes", "rendering-intent", + "resize", "rest", "rest-after", "rest-before", "richness", "right", + "rotation", "rotation-point", "ruby-align", "ruby-overhang", + "ruby-position", "ruby-span", "size", "speak", "speak-as", "speak-header", + "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set", + "tab-size", "table-layout", "target", "target-name", "target-new", + "target-position", "text-align", "text-align-last", "text-decoration", + "text-decoration-color", "text-decoration-line", "text-decoration-skip", + "text-decoration-style", "text-emphasis", "text-emphasis-color", + "text-emphasis-position", "text-emphasis-style", "text-height", + "text-indent", "text-justify", "text-outline", "text-shadow", + "text-space-collapse", "text-transform", "text-underline-position", + "text-wrap", "top", "transform", "transform-origin", "transform-style", + "transition", "transition-delay", "transition-duration", + "transition-property", "transition-timing-function", "unicode-bidi", + "vertical-align", "visibility", "voice-balance", "voice-duration", + "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress", + "voice-volume", "volume", "white-space", "widows", "width", "word-break", + "word-spacing", "word-wrap", "z-index" + ]); + + var colorKeywords = keySet([ + "black", "silver", "gray", "white", "maroon", "red", "purple", "fuchsia", + "green", "lime", "olive", "yellow", "navy", "blue", "teal", "aqua" + ]); + + var valueKeywords = keySet([ + "above", "absolute", "activeborder", "activecaption", "afar", + "after-white-space", "ahead", "alias", "all", "all-scroll", "alternate", + "always", "amharic", "amharic-abegede", "antialiased", "appworkspace", + "arabic-indic", "armenian", "asterisks", "auto", "avoid", "background", + "backwards", "baseline", "below", "bidi-override", "binary", "bengali", + "blink", "block", "block-axis", "bold", "bolder", "border", "border-box", + "both", "bottom", "break-all", "break-word", "button", "button-bevel", + "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian", + "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret", + "cell", "center", "checkbox", "circle", "cjk-earthly-branch", + "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote", + "col-resize", "collapse", "compact", "condensed", "contain", "content", + "content-box", "context-menu", "continuous", "copy", "cover", "crop", + "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", + "decimal-leading-zero", "default", "default-button", "destination-atop", + "destination-in", "destination-out", "destination-over", "devanagari", + "disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted", + "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", + "element", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", + "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er", + "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", + "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et", + "ethiopic-halehame-gez", "ethiopic-halehame-om-et", + "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et", + "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", + "ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed", + "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes", + "forwards", "from", "geometricPrecision", "georgian", "graytext", "groove", + "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew", + "help", "hidden", "hide", "higher", "highlight", "highlighttext", + "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore", + "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", + "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis", + "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", + "italic", "justify", "kannada", "katakana", "katakana-iroha", "khmer", + "landscape", "lao", "large", "larger", "left", "level", "lighter", + "line-through", "linear", "lines", "list-item", "listbox", "listitem", + "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", + "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian", + "lower-roman", "lowercase", "ltr", "malayalam", "match", + "media-controls-background", "media-current-time-display", + "media-fullscreen-button", "media-mute-button", "media-play-button", + "media-return-to-realtime-button", "media-rewind-button", + "media-seek-back-button", "media-seek-forward-button", "media-slider", + "media-sliderthumb", "media-time-remaining-display", "media-volume-slider", + "media-volume-slider-container", "media-volume-sliderthumb", "medium", + "menu", "menulist", "menulist-button", "menulist-text", + "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", + "mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize", + "narrower", "navy", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", + "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap", + "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", + "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset", + "outside", "overlay", "overline", "padding", "padding-box", "painted", + "paused", "persian", "plus-darker", "plus-lighter", "pointer", "portrait", + "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button", + "radio", "read-only", "read-write", "read-write-plaintext-only", "relative", + "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba", + "ridge", "right", "round", "row-resize", "rtl", "run-in", "running", + "s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield", + "searchfield-cancel-button", "searchfield-decoration", + "searchfield-results-button", "searchfield-results-decoration", + "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", + "single", "skip-white-space", "slide", "slider-horizontal", + "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", + "small", "small-caps", "small-caption", "smaller", "solid", "somali", + "source-atop", "source-in", "source-out", "source-over", "space", "square", + "square-button", "start", "static", "status-bar", "stretch", "stroke", + "sub", "subpixel-antialiased", "super", "sw-resize", "table", + "table-caption", "table-cell", "table-column", "table-column-group", + "table-footer-group", "table-header-group", "table-row", "table-row-group", + "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", + "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight", + "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", + "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top", + "transparent", "ultra-condensed", "ultra-expanded", "underline", "up", + "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal", + "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", + "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted", + "visibleStroke", "visual", "w-resize", "wait", "wave", "white", "wider", + "window", "windowframe", "windowtext", "x-large", "x-small", "xor", + "xx-large", "xx-small", "yellow" + ]); + + function keySet(array) { var keys = {}; for (var i = 0; i < array.length; ++i) keys[array[i]] = true; return keys; } + function ret(style, tp) {type = tp; return style;} + + function tokenBase(stream, state) { + var ch = stream.next(); + if (ch == "@") {stream.eatWhile(/[\w\\\-]/); return ret("def", stream.current());} + else if (ch == "/" && stream.eat("*")) { + state.tokenize = tokenCComment; + return tokenCComment(stream, state); + } + else if (ch == "<" && stream.eat("!")) { + state.tokenize = tokenSGMLComment; + return tokenSGMLComment(stream, state); + } + else if (ch == "=") ret(null, "compare"); + else if ((ch == "~" || ch == "|") && stream.eat("=")) return ret(null, "compare"); + else if (ch == "\"" || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + else if (ch == "#") { + stream.eatWhile(/[\w\\\-]/); + return ret("atom", "hash"); + } + else if (ch == "!") { + stream.match(/^\s*\w*/); + return ret("keyword", "important"); + } + else if (/\d/.test(ch)) { + stream.eatWhile(/[\w.%]/); + return ret("number", "unit"); + } + else if (ch === "-") { + if (/\d/.test(stream.peek())) { + stream.eatWhile(/[\w.%]/); + return ret("number", "unit"); + } else if (stream.match(/^[^-]+-/)) { + return ret("meta", type); + } + } + else if (/[,+>*\/]/.test(ch)) { + return ret(null, "select-op"); + } + else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) { + return ret("qualifier", type); + } + else if (ch == ":") { + return ret("operator", ch); + } + else if (/[;{}\[\]\(\)]/.test(ch)) { + return ret(null, ch); + } + else { + stream.eatWhile(/[\w\\\-]/); + return ret("property", "variable"); + } + } + + function tokenCComment(stream, state) { + var maybeEnd = false, ch; + while ((ch = stream.next()) != null) { + if (maybeEnd && ch == "/") { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return ret("comment", "comment"); + } + + function tokenSGMLComment(stream, state) { + var dashes = 0, ch; + while ((ch = stream.next()) != null) { + if (dashes >= 2 && ch == ">") { + state.tokenize = tokenBase; + break; + } + dashes = (ch == "-") ? dashes + 1 : 0; + } + return ret("comment", "comment"); + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) + break; + escaped = !escaped && ch == "\\"; + } + if (!escaped) state.tokenize = tokenBase; + return ret("string", "string"); + }; + } + + return { + startState: function(base) { + return {tokenize: tokenBase, + baseIndent: base || 0, + stack: []}; + }, + + token: function(stream, state) { + + // Use these terms when applicable (see http://www.xanthir.com/blog/b4E50) + // + // rule** or **ruleset: + // A selector + braces combo, or an at-rule. + // + // declaration block: + // A sequence of declarations. + // + // declaration: + // A property + colon + value combo. + // + // property value: + // The entire value of a property. + // + // component value: + // A single piece of a property value. Like the 5px in + // text-shadow: 0 0 5px blue;. Can also refer to things that are + // multiple terms, like the 1-4 terms that make up the background-size + // portion of the background shorthand. + // + // term: + // The basic unit of author-facing CSS, like a single number (5), + // dimension (5px), string ("foo"), or function. Officially defined + // by the CSS 2.1 grammar (look for the 'term' production) + // + // + // simple selector: + // A single atomic selector, like a type selector, an attr selector, a + // class selector, etc. + // + // compound selector: + // One or more simple selectors without a combinator. div.example is + // compound, div > .example is not. + // + // complex selector: + // One or more compound selectors chained with combinators. + // + // combinator: + // The parts of selectors that express relationships. There are four + // currently - the space (descendant combinator), the greater-than + // bracket (child combinator), the plus sign (next sibling combinator), + // and the tilda (following sibling combinator). + // + // sequence of selectors: + // One or more of the named type of selector chained with commas. + + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + + // Changing style returned based on context + var context = state.stack[state.stack.length-1]; + if (style == "property") { + if (context == "propertyValue"){ + if (valueKeywords[stream.current()]) { + style = "string-2"; + } else if (colorKeywords[stream.current()]) { + style = "keyword"; + } else { + style = "variable-2"; + } + } else if (context == "rule") { + if (!propertyKeywords[stream.current()]) { + style += " error"; + } + } else if (!context || context == "@media{") { + style = "tag"; + } else if (context == "@media") { + if (atMediaTypes[stream.current()]) { + style = "attribute"; // Known attribute + } else if (/^(only|not)$/i.test(stream.current())) { + style = "keyword"; + } else if (stream.current().toLowerCase() == "and") { + style = "error"; // "and" is only allowed in @mediaType + } else if (atMediaFeatures[stream.current()]) { + style = "error"; // Known property, should be in @mediaType( + } else { + // Unknown, expecting keyword or attribute, assuming attribute + style = "attribute error"; + } + } else if (context == "@mediaType") { + if (atMediaTypes[stream.current()]) { + style = "attribute"; + } else if (stream.current().toLowerCase() == "and") { + style = "operator"; + } else if (/^(only|not)$/i.test(stream.current())) { + style = "error"; // Only allowed in @media + } else if (atMediaFeatures[stream.current()]) { + style = "error"; // Known property, should be in parentheses + } else { + // Unknown attribute or property, but expecting property (preceded + // by "and"). Should be in parentheses + style = "error"; + } + } else if (context == "@mediaType(") { + if (propertyKeywords[stream.current()]) { + // do nothing, remains "property" + } else if (atMediaTypes[stream.current()]) { + style = "error"; // Known property, should be in parentheses + } else if (stream.current().toLowerCase() == "and") { + style = "operator"; + } else if (/^(only|not)$/i.test(stream.current())) { + style = "error"; // Only allowed in @media + } else { + style += " error"; + } + } else { + style = "error"; + } + } else if (style == "atom") { + if(!context || context == "@media{") { + style = "builtin"; + } else if (context == "propertyValue") { + if (!/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) { + style += " error"; + } + } else { + style = "error"; + } + } else if (context == "@media" && type == "{") { + style = "error"; + } + + // Push/pop context stack + if (type == "{") { + if (context == "@media" || context == "@mediaType") { + state.stack.pop(); + state.stack[state.stack.length-1] = "@media{"; + } + else state.stack.push("rule"); + } + else if (type == "}") { + state.stack.pop(); + if (context == "propertyValue") state.stack.pop(); + } + else if (type == "@media") state.stack.push("@media"); + else if (context == "@media" && /\b(keyword|attribute)\b/.test(style)) + state.stack.push("@mediaType"); + else if (context == "@mediaType" && stream.current() == ",") state.stack.pop(); + else if (context == "@mediaType" && type == "(") state.stack.push("@mediaType("); + else if (context == "@mediaType(" && type == ")") state.stack.pop(); + else if (context == "rule" && type == ":") state.stack.push("propertyValue"); + else if (context == "propertyValue" && type == ";") state.stack.pop(); + return style; + }, + + indent: function(state, textAfter) { + var n = state.stack.length; + if (/^\}/.test(textAfter)) + n -= state.stack[state.stack.length-1] == "propertyValue" ? 2 : 1; + return state.baseIndent + n * indentUnit; + }, + + electricChars: "}" + }; +}); + +CodeMirror.defineMIME("text/css", "css"); diff --git a/app/src/main/assets/mode/diff/diff.js b/app/src/main/assets/mode/diff/diff.js new file mode 100644 index 0000000000000000000000000000000000000000..3402f3b331b48665fc080e68579770b91c0a2e49 --- /dev/null +++ b/app/src/main/assets/mode/diff/diff.js @@ -0,0 +1,32 @@ +CodeMirror.defineMode("diff", function() { + + var TOKEN_NAMES = { + '+': 'tag', + '-': 'string', + '@': 'meta' + }; + + return { + token: function(stream) { + var tw_pos = stream.string.search(/[\t ]+?$/); + + if (!stream.sol() || tw_pos === 0) { + stream.skipToEnd(); + return ("error " + ( + TOKEN_NAMES[stream.string.charAt(0)] || '')).replace(/ $/, ''); + } + + var token_name = TOKEN_NAMES[stream.peek()] || stream.skipToEnd(); + + if (tw_pos === -1) { + stream.skipToEnd(); + } else { + stream.pos = tw_pos; + } + + return token_name; + } + }; +}); + +CodeMirror.defineMIME("text/x-diff", "diff"); diff --git a/app/src/main/assets/mode/ecl/ecl.js b/app/src/main/assets/mode/ecl/ecl.js new file mode 100644 index 0000000000000000000000000000000000000000..a8a2ee5e9a8a2c6ca58e3fc2fb0b7ecba83761ec --- /dev/null +++ b/app/src/main/assets/mode/ecl/ecl.js @@ -0,0 +1,203 @@ +CodeMirror.defineMode("ecl", function(config) { + + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + function metaHook(stream, state) { + if (!state.startOfLine) return false; + stream.skipToEnd(); + return "meta"; + } + + function tokenAtString(stream, state) { + var next; + while ((next = stream.next()) != null) { + if (next == '"' && !stream.eat('"')) { + state.tokenize = null; + break; + } + } + return "string"; + } + + var indentUnit = config.indentUnit; + var keyword = words("abs acos allnodes ascii asin asstring atan atan2 ave case choose choosen choosesets clustersize combine correlation cos cosh count covariance cron dataset dedup define denormalize distribute distributed distribution ebcdic enth error evaluate event eventextra eventname exists exp failcode failmessage fetch fromunicode getisvalid global graph group hash hash32 hash64 hashcrc hashmd5 having if index intformat isvalid iterate join keyunicode length library limit ln local log loop map matched matchlength matchposition matchtext matchunicode max merge mergejoin min nolocal nonempty normalize parse pipe power preload process project pull random range rank ranked realformat recordof regexfind regexreplace regroup rejected rollup round roundup row rowdiff sample set sin sinh sizeof soapcall sort sorted sqrt stepped stored sum table tan tanh thisnode topn tounicode transfer trim truncate typeof ungroup unicodeorder variance which workunit xmldecode xmlencode xmltext xmlunicode"); + var variable = words("apply assert build buildindex evaluate fail keydiff keypatch loadxml nothor notify output parallel sequential soapcall wait"); + var variable_2 = words("__compressed__ all and any as atmost before beginc++ best between case const counter csv descend encrypt end endc++ endmacro except exclusive expire export extend false few first flat from full function group header heading hole ifblock import in interface joined keep keyed last left limit load local locale lookup macro many maxcount maxlength min skew module named nocase noroot noscan nosort not of only opt or outer overwrite packed partition penalty physicallength pipe quote record relationship repeat return right scan self separator service shared skew skip sql store terminator thor threshold token transform trim true type unicodeorder unsorted validate virtual whole wild within xml xpath"); + var variable_3 = words("ascii big_endian boolean data decimal ebcdic integer pattern qstring real record rule set of string token udecimal unicode unsigned varstring varunicode"); + var builtin = words("checkpoint deprecated failcode failmessage failure global independent onwarning persist priority recovery stored success wait when"); + var blockKeywords = words("catch class do else finally for if switch try while"); + var atoms = words("true false null"); + var hooks = {"#": metaHook}; + var multiLineStrings; + var isOperatorChar = /[+\-*&%=<>!?|\/]/; + + var curPunc; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (hooks[ch]) { + var result = hooks[ch](stream, state); + if (result !== false) return result; + } + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + curPunc = ch; + return null; + } + if (/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + return "number"; + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "operator"; + } + stream.eatWhile(/[\w\$_]/); + var cur = stream.current().toLowerCase(); + if (keyword.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "keyword"; + } else if (variable.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "variable"; + } else if (variable_2.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "variable-2"; + } else if (variable_3.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "variable-3"; + } else if (builtin.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "builtin"; + } else { //Data types are of from KEYWORD## + var i = cur.length - 1; + while(i >= 0 && (!isNaN(cur[i]) || cur[i] == '_')) + --i; + + if (i > 0) { + var cur2 = cur.substr(0, i + 1); + if (variable_3.propertyIsEnumerable(cur2)) { + if (blockKeywords.propertyIsEnumerable(cur2)) curPunc = "newstatement"; + return "variable-3"; + } + } + } + if (atoms.propertyIsEnumerable(cur)) return "atom"; + return null; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = tokenBase; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + function pushContext(state, col, type) { + return state.context = new Context(state.indented, col, type, null, state.context); + } + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment" || style == "meta") return style; + if (ctx.align == null) ctx.align = true; + + if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); + else if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "}") { + while (ctx.type == "statement") ctx = popContext(state); + if (ctx.type == "}") ctx = popContext(state); + while (ctx.type == "statement") ctx = popContext(state); + } + else if (curPunc == ctx.type) popContext(state); + else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) + pushContext(state, stream.column(), "statement"); + state.startOfLine = false; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null) return 0; + var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); + if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; + var closing = firstChar == ctx.type; + if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit); + else if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indented + (closing ? 0 : indentUnit); + }, + + electricChars: "{}" + }; +}); + +CodeMirror.defineMIME("text/x-ecl", "ecl"); diff --git a/app/src/main/assets/mode/erlang/erlang.js b/app/src/main/assets/mode/erlang/erlang.js new file mode 100644 index 0000000000000000000000000000000000000000..e79ab7668876d0c1d703f10be61b83f29164296c --- /dev/null +++ b/app/src/main/assets/mode/erlang/erlang.js @@ -0,0 +1,463 @@ +// block; "begin", "case", "fun", "if", "receive", "try": closed by "end" +// block internal; "after", "catch", "of" +// guard; "when", closed by "->" +// "->" opens a clause, closed by ";" or "." +// "<<" opens a binary, closed by ">>" +// "," appears in arglists, lists, tuples and terminates lines of code +// "." resets indentation to 0 +// obsolete; "cond", "let", "query" + +CodeMirror.defineMIME("text/x-erlang", "erlang"); + +CodeMirror.defineMode("erlang", function(cmCfg, modeCfg) { + + function rval(state,stream,type) { + // distinguish between "." as terminator and record field operator + if (type == "record") { + state.context = "record"; + }else{ + state.context = false; + } + + // remember last significant bit on last line for indenting + if (type != "whitespace" && type != "comment") { + state.lastToken = stream.current(); + } + // erlang -> CodeMirror tag + switch (type) { + case "atom": return "atom"; + case "attribute": return "attribute"; + case "builtin": return "builtin"; + case "comment": return "comment"; + case "fun": return "meta"; + case "function": return "tag"; + case "guard": return "property"; + case "keyword": return "keyword"; + case "macro": return "variable-2"; + case "number": return "number"; + case "operator": return "operator"; + case "record": return "bracket"; + case "string": return "string"; + case "type": return "def"; + case "variable": return "variable"; + case "error": return "error"; + case "separator": return null; + case "open_paren": return null; + case "close_paren": return null; + default: return null; + } + } + + var typeWords = [ + "-type", "-spec", "-export_type", "-opaque"]; + + var keywordWords = [ + "after","begin","catch","case","cond","end","fun","if", + "let","of","query","receive","try","when"]; + + var separatorWords = [ + "->",";",":",".",","]; + + var operatorWords = [ + "and","andalso","band","bnot","bor","bsl","bsr","bxor", + "div","not","or","orelse","rem","xor"]; + + var symbolWords = [ + "+","-","*","/",">",">=","<","=<","=:=","==","=/=","/=","||","<-"]; + + var openParenWords = [ + "<<","(","[","{"]; + + var closeParenWords = [ + "}","]",")",">>"]; + + var guardWords = [ + "is_atom","is_binary","is_bitstring","is_boolean","is_float", + "is_function","is_integer","is_list","is_number","is_pid", + "is_port","is_record","is_reference","is_tuple", + "atom","binary","bitstring","boolean","function","integer","list", + "number","pid","port","record","reference","tuple"]; + + var bifWords = [ + "abs","adler32","adler32_combine","alive","apply","atom_to_binary", + "atom_to_list","binary_to_atom","binary_to_existing_atom", + "binary_to_list","binary_to_term","bit_size","bitstring_to_list", + "byte_size","check_process_code","contact_binary","crc32", + "crc32_combine","date","decode_packet","delete_module", + "disconnect_node","element","erase","exit","float","float_to_list", + "garbage_collect","get","get_keys","group_leader","halt","hd", + "integer_to_list","internal_bif","iolist_size","iolist_to_binary", + "is_alive","is_atom","is_binary","is_bitstring","is_boolean", + "is_float","is_function","is_integer","is_list","is_number","is_pid", + "is_port","is_process_alive","is_record","is_reference","is_tuple", + "length","link","list_to_atom","list_to_binary","list_to_bitstring", + "list_to_existing_atom","list_to_float","list_to_integer", + "list_to_pid","list_to_tuple","load_module","make_ref","module_loaded", + "monitor_node","node","node_link","node_unlink","nodes","notalive", + "now","open_port","pid_to_list","port_close","port_command", + "port_connect","port_control","pre_loaded","process_flag", + "process_info","processes","purge_module","put","register", + "registered","round","self","setelement","size","spawn","spawn_link", + "spawn_monitor","spawn_opt","split_binary","statistics", + "term_to_binary","time","throw","tl","trunc","tuple_size", + "tuple_to_list","unlink","unregister","whereis"]; + + // ignored for indenting purposes + var ignoreWords = [ + ",", ":", "catch", "after", "of", "cond", "let", "query"]; + + + var smallRE = /[a-z_]/; + var largeRE = /[A-Z_]/; + var digitRE = /[0-9]/; + var octitRE = /[0-7]/; + var anumRE = /[a-z_A-Z0-9]/; + var symbolRE = /[\+\-\*\/<>=\|:]/; + var openParenRE = /[<\(\[\{]/; + var closeParenRE = /[>\)\]\}]/; + var sepRE = /[\->\.,:;]/; + + function isMember(element,list) { + return (-1 < list.indexOf(element)); + } + + function isPrev(stream,string) { + var start = stream.start; + var len = string.length; + if (len <= start) { + var word = stream.string.slice(start-len,start); + return word == string; + }else{ + return false; + } + } + + function tokenize(stream, state) { + if (stream.eatSpace()) { + return rval(state,stream,"whitespace"); + } + + // attributes and type specs + if ((peekToken(state).token == "" || peekToken(state).token == ".") && + stream.peek() == '-') { + stream.next(); + if (stream.eat(smallRE) && stream.eatWhile(anumRE)) { + if (isMember(stream.current(),typeWords)) { + return rval(state,stream,"type"); + }else{ + return rval(state,stream,"attribute"); + } + } + stream.backUp(1); + } + + var ch = stream.next(); + + // comment + if (ch == '%') { + stream.skipToEnd(); + return rval(state,stream,"comment"); + } + + // macro + if (ch == '?') { + stream.eatWhile(anumRE); + return rval(state,stream,"macro"); + } + + // record + if ( ch == "#") { + stream.eatWhile(anumRE); + return rval(state,stream,"record"); + } + + // char + if ( ch == "$") { + if (stream.next() == "\\") { + if (!stream.eatWhile(octitRE)) { + stream.next(); + } + } + return rval(state,stream,"string"); + } + + // quoted atom + if (ch == '\'') { + if (singleQuote(stream)) { + return rval(state,stream,"atom"); + }else{ + return rval(state,stream,"error"); + } + } + + // string + if (ch == '"') { + if (doubleQuote(stream)) { + return rval(state,stream,"string"); + }else{ + return rval(state,stream,"error"); + } + } + + // variable + if (largeRE.test(ch)) { + stream.eatWhile(anumRE); + return rval(state,stream,"variable"); + } + + // atom/keyword/BIF/function + if (smallRE.test(ch)) { + stream.eatWhile(anumRE); + + if (stream.peek() == "/") { + stream.next(); + if (stream.eatWhile(digitRE)) { + return rval(state,stream,"fun"); // f/0 style fun + }else{ + stream.backUp(1); + return rval(state,stream,"atom"); + } + } + + var w = stream.current(); + + if (isMember(w,keywordWords)) { + pushToken(state,stream); + return rval(state,stream,"keyword"); + } + if (stream.peek() == "(") { + // 'put' and 'erlang:put' are bifs, 'foo:put' is not + if (isMember(w,bifWords) && + (!isPrev(stream,":") || isPrev(stream,"erlang:"))) { + return rval(state,stream,"builtin"); + }else{ + return rval(state,stream,"function"); + } + } + if (isMember(w,guardWords)) { + return rval(state,stream,"guard"); + } + if (isMember(w,operatorWords)) { + return rval(state,stream,"operator"); + } + if (stream.peek() == ":") { + if (w == "erlang") { + return rval(state,stream,"builtin"); + } else { + return rval(state,stream,"function"); + } + } + return rval(state,stream,"atom"); + } + + // number + if (digitRE.test(ch)) { + stream.eatWhile(digitRE); + if (stream.eat('#')) { + stream.eatWhile(digitRE); // 16#10 style integer + } else { + if (stream.eat('.')) { // float + stream.eatWhile(digitRE); + } + if (stream.eat(/[eE]/)) { + stream.eat(/[-+]/); // float with exponent + stream.eatWhile(digitRE); + } + } + return rval(state,stream,"number"); // normal integer + } + + // open parens + if (nongreedy(stream,openParenRE,openParenWords)) { + pushToken(state,stream); + return rval(state,stream,"open_paren"); + } + + // close parens + if (nongreedy(stream,closeParenRE,closeParenWords)) { + pushToken(state,stream); + return rval(state,stream,"close_paren"); + } + + // separators + if (greedy(stream,sepRE,separatorWords)) { + // distinguish between "." as terminator and record field operator + if (state.context == false) { + pushToken(state,stream); + } + return rval(state,stream,"separator"); + } + + // operators + if (greedy(stream,symbolRE,symbolWords)) { + return rval(state,stream,"operator"); + } + + return rval(state,stream,null); + } + + function nongreedy(stream,re,words) { + if (stream.current().length == 1 && re.test(stream.current())) { + stream.backUp(1); + while (re.test(stream.peek())) { + stream.next(); + if (isMember(stream.current(),words)) { + return true; + } + } + stream.backUp(stream.current().length-1); + } + return false; + } + + function greedy(stream,re,words) { + if (stream.current().length == 1 && re.test(stream.current())) { + while (re.test(stream.peek())) { + stream.next(); + } + while (0 < stream.current().length) { + if (isMember(stream.current(),words)) { + return true; + }else{ + stream.backUp(1); + } + } + stream.next(); + } + return false; + } + + function doubleQuote(stream) { + return quote(stream, '"', '\\'); + } + + function singleQuote(stream) { + return quote(stream,'\'','\\'); + } + + function quote(stream,quoteChar,escapeChar) { + while (!stream.eol()) { + var ch = stream.next(); + if (ch == quoteChar) { + return true; + }else if (ch == escapeChar) { + stream.next(); + } + } + return false; + } + + function Token(stream) { + this.token = stream ? stream.current() : ""; + this.column = stream ? stream.column() : 0; + this.indent = stream ? stream.indentation() : 0; + } + + function myIndent(state,textAfter) { + var indent = cmCfg.indentUnit; + var outdentWords = ["after","catch"]; + var token = (peekToken(state)).token; + var wordAfter = takewhile(textAfter,/[^a-z]/); + + if (isMember(token,openParenWords)) { + return (peekToken(state)).column+token.length; + }else if (token == "." || token == ""){ + return 0; + }else if (token == "->") { + if (wordAfter == "end") { + return peekToken(state,2).column; + }else if (peekToken(state,2).token == "fun") { + return peekToken(state,2).column+indent; + }else{ + return (peekToken(state)).indent+indent; + } + }else if (isMember(wordAfter,outdentWords)) { + return (peekToken(state)).indent; + }else{ + return (peekToken(state)).column+indent; + } + } + + function takewhile(str,re) { + var m = str.match(re); + return m ? str.slice(0,m.index) : str; + } + + function popToken(state) { + return state.tokenStack.pop(); + } + + function peekToken(state,depth) { + var len = state.tokenStack.length; + var dep = (depth ? depth : 1); + if (len < dep) { + return new Token; + }else{ + return state.tokenStack[len-dep]; + } + } + + function pushToken(state,stream) { + var token = stream.current(); + var prev_token = peekToken(state).token; + if (isMember(token,ignoreWords)) { + return false; + }else if (drop_both(prev_token,token)) { + popToken(state); + return false; + }else if (drop_first(prev_token,token)) { + popToken(state); + return pushToken(state,stream); + }else{ + state.tokenStack.push(new Token(stream)); + return true; + } + } + + function drop_first(open, close) { + switch (open+" "+close) { + case "when ->": return true; + case "-> end": return true; + case "-> .": return true; + case ". .": return true; + default: return false; + } + } + + function drop_both(open, close) { + switch (open+" "+close) { + case "( )": return true; + case "[ ]": return true; + case "{ }": return true; + case "<< >>": return true; + case "begin end": return true; + case "case end": return true; + case "fun end": return true; + case "if end": return true; + case "receive end": return true; + case "try end": return true; + case "-> ;": return true; + default: return false; + } + } + + return { + startState: + function() { + return {tokenStack: [], + context: false, + lastToken: null}; + }, + + token: + function(stream, state) { + return tokenize(stream, state); + }, + + indent: + function(state, textAfter) { +// console.log(state.tokenStack); + return myIndent(state,textAfter); + } + }; +}); diff --git a/app/src/main/assets/mode/gfm/gfm.js b/app/src/main/assets/mode/gfm/gfm.js new file mode 100644 index 0000000000000000000000000000000000000000..f8a76ee9273bb66ab5294e84c4e9a327d36d62f0 --- /dev/null +++ b/app/src/main/assets/mode/gfm/gfm.js @@ -0,0 +1,94 @@ +CodeMirror.defineMode("gfm", function(config, parserConfig) { + var codeDepth = 0; + function blankLine(state) { + state.code = false; + return null; + } + var gfmOverlay = { + startState: function() { + return { + code: false, + codeBlock: false, + ateSpace: false + }; + }, + copyState: function(s) { + return { + code: s.code, + codeBlock: s.codeBlock, + ateSpace: s.ateSpace + }; + }, + token: function(stream, state) { + // Hack to prevent formatting override inside code blocks (block and inline) + if (state.codeBlock) { + if (stream.match(/^```/)) { + state.codeBlock = false; + return null; + } + stream.skipToEnd(); + return null; + } + if (stream.sol()) { + state.code = false; + } + if (stream.sol() && stream.match(/^```/)) { + stream.skipToEnd(); + state.codeBlock = true; + return null; + } + // If this block is changed, it may need to be updated in Markdown mode + if (stream.peek() === '`') { + stream.next(); + var before = stream.pos; + stream.eatWhile('`'); + var difference = 1 + stream.pos - before; + if (!state.code) { + codeDepth = difference; + state.code = true; + } else { + if (difference === codeDepth) { // Must be exact + state.code = false; + } + } + return null; + } else if (state.code) { + stream.next(); + return null; + } + // Check if space. If so, links can be formatted later on + if (stream.eatSpace()) { + state.ateSpace = true; + return null; + } + if (stream.sol() || state.ateSpace) { + state.ateSpace = false; + if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/)) { + // User/Project@SHA + // User@SHA + // SHA + return "link"; + } else if (stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/)) { + // User/Project#Num + // User#Num + // #Num + return "link"; + } + } + if (stream.match(/^((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i)) { + // URLs + // Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls + return "link"; + } + stream.next(); + return null; + }, + blankLine: blankLine + }; + CodeMirror.defineMIME("gfmBase", { + name: "markdown", + underscoresBreakWords: false, + fencedCodeBlocks: true + }); + return CodeMirror.overlayMode(CodeMirror.getMode(config, "gfmBase"), gfmOverlay); +}, "markdown"); diff --git a/app/src/main/assets/mode/go/go.js b/app/src/main/assets/mode/go/go.js new file mode 100644 index 0000000000000000000000000000000000000000..9863bbf0439e0f479ca9b89b051810a2657e72b0 --- /dev/null +++ b/app/src/main/assets/mode/go/go.js @@ -0,0 +1,170 @@ +CodeMirror.defineMode("go", function(config, parserConfig) { + var indentUnit = config.indentUnit; + + var keywords = { + "break":true, "case":true, "chan":true, "const":true, "continue":true, + "default":true, "defer":true, "else":true, "fallthrough":true, "for":true, + "func":true, "go":true, "goto":true, "if":true, "import":true, + "interface":true, "map":true, "package":true, "range":true, "return":true, + "select":true, "struct":true, "switch":true, "type":true, "var":true, + "bool":true, "byte":true, "complex64":true, "complex128":true, + "float32":true, "float64":true, "int8":true, "int16":true, "int32":true, + "int64":true, "string":true, "uint8":true, "uint16":true, "uint32":true, + "uint64":true, "int":true, "uint":true, "uintptr":true + }; + + var atoms = { + "true":true, "false":true, "iota":true, "nil":true, "append":true, + "cap":true, "close":true, "complex":true, "copy":true, "imag":true, + "len":true, "make":true, "new":true, "panic":true, "print":true, + "println":true, "real":true, "recover":true + }; + + var blockKeywords = { + "else":true, "for":true, "func":true, "if":true, "interface":true, + "select":true, "struct":true, "switch":true + }; + + var isOperatorChar = /[+\-*&^%:=<>!|\/]/; + + var curPunc; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"' || ch == "'" || ch == "`") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (/[\d\.]/.test(ch)) { + if (ch == ".") { + stream.match(/^[0-9]+([eE][\-+]?[0-9]+)?/); + } else if (ch == "0") { + stream.match(/^[xX][0-9a-fA-F]+/) || stream.match(/^0[0-7]+/); + } else { + stream.match(/^[0-9]*\.?[0-9]*([eE][\-+]?[0-9]+)?/); + } + return "number"; + } + if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + curPunc = ch; + return null; + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "operator"; + } + stream.eatWhile(/[\w\$_]/); + var cur = stream.current(); + if (keywords.propertyIsEnumerable(cur)) { + if (cur == "case" || cur == "default") curPunc = "case"; + return "keyword"; + } + if (atoms.propertyIsEnumerable(cur)) return "atom"; + return "variable"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || quote == "`")) + state.tokenize = tokenBase; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + function pushContext(state, col, type) { + return state.context = new Context(state.indented, col, type, null, state.context); + } + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + if (ctx.type == "case") ctx.type = "}"; + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment") return style; + if (ctx.align == null) ctx.align = true; + + if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "case") ctx.type = "case"; + else if (curPunc == "}" && ctx.type == "}") ctx = popContext(state); + else if (curPunc == ctx.type) popContext(state); + state.startOfLine = false; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null) return 0; + var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); + if (ctx.type == "case" && /^(?:case|default)\b/.test(textAfter)) { + state.context.type = "}"; + return ctx.indented; + } + var closing = firstChar == ctx.type; + if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indented + (closing ? 0 : indentUnit); + }, + + electricChars: "{}:" + }; +}); + +CodeMirror.defineMIME("text/x-go", "go"); diff --git a/app/src/main/assets/mode/groovy/groovy.js b/app/src/main/assets/mode/groovy/groovy.js new file mode 100644 index 0000000000000000000000000000000000000000..752bc2f7d338ef4ea91dd3f5fda2f52016fbcbb6 --- /dev/null +++ b/app/src/main/assets/mode/groovy/groovy.js @@ -0,0 +1,210 @@ +CodeMirror.defineMode("groovy", function(config, parserConfig) { + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + var keywords = words( + "abstract as assert boolean break byte case catch char class const continue def default " + + "do double else enum extends final finally float for goto if implements import in " + + "instanceof int interface long native new package private protected public return " + + "short static strictfp super switch synchronized threadsafe throw throws transient " + + "try void volatile while"); + var blockKeywords = words("catch class do else finally for if switch try while enum interface def"); + var atoms = words("null true false this"); + + var curPunc; + function tokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"' || ch == "'") { + return startString(ch, stream, state); + } + if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + curPunc = ch; + return null; + } + if (/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + if (stream.eat(/eE/)) { stream.eat(/\+\-/); stream.eatWhile(/\d/); } + return "number"; + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize.push(tokenComment); + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + if (expectExpression(state.lastToken)) { + return startString(ch, stream, state); + } + } + if (ch == "-" && stream.eat(">")) { + curPunc = "->"; + return null; + } + if (/[+\-*&%=<>!?|\/~]/.test(ch)) { + stream.eatWhile(/[+\-*&%=<>|~]/); + return "operator"; + } + stream.eatWhile(/[\w\$_]/); + if (ch == "@") { stream.eatWhile(/[\w\$_\.]/); return "meta"; } + if (state.lastToken == ".") return "property"; + if (stream.eat(":")) { curPunc = "proplabel"; return "property"; } + var cur = stream.current(); + if (atoms.propertyIsEnumerable(cur)) { return "atom"; } + if (keywords.propertyIsEnumerable(cur)) { + if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; + return "keyword"; + } + return "variable"; + } + tokenBase.isBase = true; + + function startString(quote, stream, state) { + var tripleQuoted = false; + if (quote != "/" && stream.eat(quote)) { + if (stream.eat(quote)) tripleQuoted = true; + else return "string"; + } + function t(stream, state) { + var escaped = false, next, end = !tripleQuoted; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) { + if (!tripleQuoted) { break; } + if (stream.match(quote + quote)) { end = true; break; } + } + if (quote == '"' && next == "$" && !escaped && stream.eat("{")) { + state.tokenize.push(tokenBaseUntilBrace()); + return "string"; + } + escaped = !escaped && next == "\\"; + } + if (end) state.tokenize.pop(); + return "string"; + } + state.tokenize.push(t); + return t(stream, state); + } + + function tokenBaseUntilBrace() { + var depth = 1; + function t(stream, state) { + if (stream.peek() == "}") { + depth--; + if (depth == 0) { + state.tokenize.pop(); + return state.tokenize[state.tokenize.length-1](stream, state); + } + } else if (stream.peek() == "{") { + depth++; + } + return tokenBase(stream, state); + } + t.isBase = true; + return t; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize.pop(); + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function expectExpression(last) { + return !last || last == "operator" || last == "->" || /[\.\[\{\(,;:]/.test(last) || + last == "newstatement" || last == "keyword" || last == "proplabel"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + function pushContext(state, col, type) { + return state.context = new Context(state.indented, col, type, null, state.context); + } + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: [tokenBase], + context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false), + indented: 0, + startOfLine: true, + lastToken: null + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + // Automatic semicolon insertion + if (ctx.type == "statement" && !expectExpression(state.lastToken)) { + popContext(state); ctx = state.context; + } + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = state.tokenize[state.tokenize.length-1](stream, state); + if (style == "comment") return style; + if (ctx.align == null) ctx.align = true; + + if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); + // Handle indentation for {x -> \n ... } + else if (curPunc == "->" && ctx.type == "statement" && ctx.prev.type == "}") { + popContext(state); + state.context.align = false; + } + else if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "}") { + while (ctx.type == "statement") ctx = popContext(state); + if (ctx.type == "}") ctx = popContext(state); + while (ctx.type == "statement") ctx = popContext(state); + } + else if (curPunc == ctx.type) popContext(state); + else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) + pushContext(state, stream.column(), "statement"); + state.startOfLine = false; + state.lastToken = curPunc || style; + return style; + }, + + indent: function(state, textAfter) { + if (!state.tokenize[state.tokenize.length-1].isBase) return 0; + var firstChar = textAfter && textAfter.charAt(0), ctx = state.context; + if (ctx.type == "statement" && !expectExpression(state.lastToken)) ctx = ctx.prev; + var closing = firstChar == ctx.type; + if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : config.indentUnit); + else if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indented + (closing ? 0 : config.indentUnit); + }, + + electricChars: "{}" + }; +}); + +CodeMirror.defineMIME("text/x-groovy", "groovy"); diff --git a/app/src/main/assets/mode/haskell/haskell.js b/app/src/main/assets/mode/haskell/haskell.js new file mode 100644 index 0000000000000000000000000000000000000000..e15a32cdf3e7881132901d21a95500e2ded309c0 --- /dev/null +++ b/app/src/main/assets/mode/haskell/haskell.js @@ -0,0 +1,242 @@ +CodeMirror.defineMode("haskell", function(cmCfg, modeCfg) { + + function switchState(source, setState, f) { + setState(f); + return f(source, setState); + } + + // These should all be Unicode extended, as per the Haskell 2010 report + var smallRE = /[a-z_]/; + var largeRE = /[A-Z]/; + var digitRE = /[0-9]/; + var hexitRE = /[0-9A-Fa-f]/; + var octitRE = /[0-7]/; + var idRE = /[a-z_A-Z0-9']/; + var symbolRE = /[-!#$%&*+.\/<=>?@\\^|~:]/; + var specialRE = /[(),;[\]`{}]/; + var whiteCharRE = /[ \t\v\f]/; // newlines are handled in tokenizer + + function normal(source, setState) { + if (source.eatWhile(whiteCharRE)) { + return null; + } + + var ch = source.next(); + if (specialRE.test(ch)) { + if (ch == '{' && source.eat('-')) { + var t = "comment"; + if (source.eat('#')) { + t = "meta"; + } + return switchState(source, setState, ncomment(t, 1)); + } + return null; + } + + if (ch == '\'') { + if (source.eat('\\')) { + source.next(); // should handle other escapes here + } + else { + source.next(); + } + if (source.eat('\'')) { + return "string"; + } + return "error"; + } + + if (ch == '"') { + return switchState(source, setState, stringLiteral); + } + + if (largeRE.test(ch)) { + source.eatWhile(idRE); + if (source.eat('.')) { + return "qualifier"; + } + return "variable-2"; + } + + if (smallRE.test(ch)) { + source.eatWhile(idRE); + return "variable"; + } + + if (digitRE.test(ch)) { + if (ch == '0') { + if (source.eat(/[xX]/)) { + source.eatWhile(hexitRE); // should require at least 1 + return "integer"; + } + if (source.eat(/[oO]/)) { + source.eatWhile(octitRE); // should require at least 1 + return "number"; + } + } + source.eatWhile(digitRE); + var t = "number"; + if (source.eat('.')) { + t = "number"; + source.eatWhile(digitRE); // should require at least 1 + } + if (source.eat(/[eE]/)) { + t = "number"; + source.eat(/[-+]/); + source.eatWhile(digitRE); // should require at least 1 + } + return t; + } + + if (symbolRE.test(ch)) { + if (ch == '-' && source.eat(/-/)) { + source.eatWhile(/-/); + if (!source.eat(symbolRE)) { + source.skipToEnd(); + return "comment"; + } + } + var t = "variable"; + if (ch == ':') { + t = "variable-2"; + } + source.eatWhile(symbolRE); + return t; + } + + return "error"; + } + + function ncomment(type, nest) { + if (nest == 0) { + return normal; + } + return function(source, setState) { + var currNest = nest; + while (!source.eol()) { + var ch = source.next(); + if (ch == '{' && source.eat('-')) { + ++currNest; + } + else if (ch == '-' && source.eat('}')) { + --currNest; + if (currNest == 0) { + setState(normal); + return type; + } + } + } + setState(ncomment(type, currNest)); + return type; + }; + } + + function stringLiteral(source, setState) { + while (!source.eol()) { + var ch = source.next(); + if (ch == '"') { + setState(normal); + return "string"; + } + if (ch == '\\') { + if (source.eol() || source.eat(whiteCharRE)) { + setState(stringGap); + return "string"; + } + if (source.eat('&')) { + } + else { + source.next(); // should handle other escapes here + } + } + } + setState(normal); + return "error"; + } + + function stringGap(source, setState) { + if (source.eat('\\')) { + return switchState(source, setState, stringLiteral); + } + source.next(); + setState(normal); + return "error"; + } + + + var wellKnownWords = (function() { + var wkw = {}; + function setType(t) { + return function () { + for (var i = 0; i < arguments.length; i++) + wkw[arguments[i]] = t; + }; + } + + setType("keyword")( + "case", "class", "data", "default", "deriving", "do", "else", "foreign", + "if", "import", "in", "infix", "infixl", "infixr", "instance", "let", + "module", "newtype", "of", "then", "type", "where", "_"); + + setType("keyword")( + "\.\.", ":", "::", "=", "\\", "\"", "<-", "->", "@", "~", "=>"); + + setType("builtin")( + "!!", "$!", "$", "&&", "+", "++", "-", ".", "/", "/=", "<", "<=", "=<<", + "==", ">", ">=", ">>", ">>=", "^", "^^", "||", "*", "**"); + + setType("builtin")( + "Bool", "Bounded", "Char", "Double", "EQ", "Either", "Enum", "Eq", + "False", "FilePath", "Float", "Floating", "Fractional", "Functor", "GT", + "IO", "IOError", "Int", "Integer", "Integral", "Just", "LT", "Left", + "Maybe", "Monad", "Nothing", "Num", "Ord", "Ordering", "Rational", "Read", + "ReadS", "Real", "RealFloat", "RealFrac", "Right", "Show", "ShowS", + "String", "True"); + + setType("builtin")( + "abs", "acos", "acosh", "all", "and", "any", "appendFile", "asTypeOf", + "asin", "asinh", "atan", "atan2", "atanh", "break", "catch", "ceiling", + "compare", "concat", "concatMap", "const", "cos", "cosh", "curry", + "cycle", "decodeFloat", "div", "divMod", "drop", "dropWhile", "either", + "elem", "encodeFloat", "enumFrom", "enumFromThen", "enumFromThenTo", + "enumFromTo", "error", "even", "exp", "exponent", "fail", "filter", + "flip", "floatDigits", "floatRadix", "floatRange", "floor", "fmap", + "foldl", "foldl1", "foldr", "foldr1", "fromEnum", "fromInteger", + "fromIntegral", "fromRational", "fst", "gcd", "getChar", "getContents", + "getLine", "head", "id", "init", "interact", "ioError", "isDenormalized", + "isIEEE", "isInfinite", "isNaN", "isNegativeZero", "iterate", "last", + "lcm", "length", "lex", "lines", "log", "logBase", "lookup", "map", + "mapM", "mapM_", "max", "maxBound", "maximum", "maybe", "min", "minBound", + "minimum", "mod", "negate", "not", "notElem", "null", "odd", "or", + "otherwise", "pi", "pred", "print", "product", "properFraction", + "putChar", "putStr", "putStrLn", "quot", "quotRem", "read", "readFile", + "readIO", "readList", "readLn", "readParen", "reads", "readsPrec", + "realToFrac", "recip", "rem", "repeat", "replicate", "return", "reverse", + "round", "scaleFloat", "scanl", "scanl1", "scanr", "scanr1", "seq", + "sequence", "sequence_", "show", "showChar", "showList", "showParen", + "showString", "shows", "showsPrec", "significand", "signum", "sin", + "sinh", "snd", "span", "splitAt", "sqrt", "subtract", "succ", "sum", + "tail", "take", "takeWhile", "tan", "tanh", "toEnum", "toInteger", + "toRational", "truncate", "uncurry", "undefined", "unlines", "until", + "unwords", "unzip", "unzip3", "userError", "words", "writeFile", "zip", + "zip3", "zipWith", "zipWith3"); + + return wkw; + })(); + + + + return { + startState: function () { return { f: normal }; }, + copyState: function (s) { return { f: s.f }; }, + + token: function(stream, state) { + var t = state.f(stream, function(s) { state.f = s; }); + var w = stream.current(); + return (w in wellKnownWords) ? wellKnownWords[w] : t; + } + }; + +}); + +CodeMirror.defineMIME("text/x-haskell", "haskell"); diff --git a/app/src/main/assets/mode/haxe/haxe.js b/app/src/main/assets/mode/haxe/haxe.js new file mode 100644 index 0000000000000000000000000000000000000000..64f4eb3ff876b78f8a49d820d21b230ff0a6e06e --- /dev/null +++ b/app/src/main/assets/mode/haxe/haxe.js @@ -0,0 +1,429 @@ +CodeMirror.defineMode("haxe", function(config, parserConfig) { + var indentUnit = config.indentUnit; + + // Tokenizer + + var keywords = function(){ + function kw(type) {return {type: type, style: "keyword"};} + var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); + var operator = kw("operator"), atom = {type: "atom", style: "atom"}, attribute = {type:"attribute", style: "attribute"}; + var type = kw("typedef"); + return { + "if": A, "while": A, "else": B, "do": B, "try": B, + "return": C, "break": C, "continue": C, "new": C, "throw": C, + "var": kw("var"), "inline":attribute, "static": attribute, "using":kw("import"), + "public": attribute, "private": attribute, "cast": kw("cast"), "import": kw("import"), "macro": kw("macro"), + "function": kw("function"), "catch": kw("catch"), "untyped": kw("untyped"), "callback": kw("cb"), + "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), + "in": operator, "never": kw("property_access"), "trace":kw("trace"), + "class": type, "enum":type, "interface":type, "typedef":type, "extends":type, "implements":type, "dynamic":type, + "true": atom, "false": atom, "null": atom + }; + }(); + + var isOperatorChar = /[+\-*&%=<>!?|]/; + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + + function nextUntilUnescaped(stream, end) { + var escaped = false, next; + while ((next = stream.next()) != null) { + if (next == end && !escaped) + return false; + escaped = !escaped && next == "\\"; + } + return escaped; + } + + // Used as scratch variables to communicate multiple values without + // consing up tons of objects. + var type, content; + function ret(tp, style, cont) { + type = tp; content = cont; + return style; + } + + function haxeTokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"' || ch == "'") + return chain(stream, state, haxeTokenString(ch)); + else if (/[\[\]{}\(\),;\:\.]/.test(ch)) + return ret(ch); + else if (ch == "0" && stream.eat(/x/i)) { + stream.eatWhile(/[\da-f]/i); + return ret("number", "number"); + } + else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) { + stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); + return ret("number", "number"); + } + else if (state.reAllowed && (ch == "~" && stream.eat(/\//))) { + nextUntilUnescaped(stream, "/"); + stream.eatWhile(/[gimsu]/); + return ret("regexp", "string-2"); + } + else if (ch == "/") { + if (stream.eat("*")) { + return chain(stream, state, haxeTokenComment); + } + else if (stream.eat("/")) { + stream.skipToEnd(); + return ret("comment", "comment"); + } + else { + stream.eatWhile(isOperatorChar); + return ret("operator", null, stream.current()); + } + } + else if (ch == "#") { + stream.skipToEnd(); + return ret("conditional", "meta"); + } + else if (ch == "@") { + stream.eat(/:/); + stream.eatWhile(/[\w_]/); + return ret ("metadata", "meta"); + } + else if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return ret("operator", null, stream.current()); + } + else { + var word; + if(/[A-Z]/.test(ch)) + { + stream.eatWhile(/[\w_<>]/); + word = stream.current(); + return ret("type", "variable-3", word); + } + else + { + stream.eatWhile(/[\w_]/); + var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; + return (known && state.kwAllowed) ? ret(known.type, known.style, word) : + ret("variable", "variable", word); + } + } + } + + function haxeTokenString(quote) { + return function(stream, state) { + if (!nextUntilUnescaped(stream, quote)) + state.tokenize = haxeTokenBase; + return ret("string", "string"); + }; + } + + function haxeTokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = haxeTokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return ret("comment", "comment"); + } + + // Parser + + var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; + + function HaxeLexical(indented, column, type, align, prev, info) { + this.indented = indented; + this.column = column; + this.type = type; + this.prev = prev; + this.info = info; + if (align != null) this.align = align; + } + + function inScope(state, varname) { + for (var v = state.localVars; v; v = v.next) + if (v.name == varname) return true; + } + + function parseHaxe(state, style, type, content, stream) { + var cc = state.cc; + // Communicate our context to the combinators. + // (Less wasteful than consing up a hundred closures on every call.) + cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; + + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = true; + + while(true) { + var combinator = cc.length ? cc.pop() : statement; + if (combinator(type, content)) { + while(cc.length && cc[cc.length - 1].lex) + cc.pop()(); + if (cx.marked) return cx.marked; + if (type == "variable" && inScope(state, content)) return "variable-2"; + if (type == "variable" && imported(state, content)) return "variable-3"; + return style; + } + } + } + + function imported(state, typename) + { + if (/[a-z]/.test(typename.charAt(0))) + return false; + var len = state.importedtypes.length; + for (var i = 0; i= 0; i--) cx.cc.push(arguments[i]); + } + function cont() { + pass.apply(null, arguments); + return true; + } + function register(varname) { + var state = cx.state; + if (state.context) { + cx.marked = "def"; + for (var v = state.localVars; v; v = v.next) + if (v.name == varname) return; + state.localVars = {name: varname, next: state.localVars}; + } + } + + // Combinators + + var defaultVars = {name: "this", next: null}; + function pushcontext() { + if (!cx.state.context) cx.state.localVars = defaultVars; + cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; + } + function popcontext() { + cx.state.localVars = cx.state.context.vars; + cx.state.context = cx.state.context.prev; + } + function pushlex(type, info) { + var result = function() { + var state = cx.state; + state.lexical = new HaxeLexical(state.indented, cx.stream.column(), type, null, state.lexical, info); + }; + result.lex = true; + return result; + } + function poplex() { + var state = cx.state; + if (state.lexical.prev) { + if (state.lexical.type == ")") + state.indented = state.lexical.indented; + state.lexical = state.lexical.prev; + } + } + poplex.lex = true; + + function expect(wanted) { + return function expecting(type) { + if (type == wanted) return cont(); + else if (wanted == ";") return pass(); + else return cont(arguments.callee); + }; + } + + function statement(type) { + if (type == "@") return cont(metadef); + if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); + if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); + if (type == "keyword b") return cont(pushlex("form"), statement, poplex); + if (type == "{") return cont(pushlex("}"), pushcontext, block, poplex, popcontext); + if (type == ";") return cont(); + if (type == "attribute") return cont(maybeattribute); + if (type == "function") return cont(functiondef); + if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), + poplex, statement, poplex); + if (type == "variable") return cont(pushlex("stat"), maybelabel); + if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), + block, poplex, poplex); + if (type == "case") return cont(expression, expect(":")); + if (type == "default") return cont(expect(":")); + if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), + statement, poplex, popcontext); + if (type == "import") return cont(importdef, expect(";")); + if (type == "typedef") return cont(typedef); + return pass(pushlex("stat"), expression, expect(";"), poplex); + } + function expression(type) { + if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator); + if (type == "function") return cont(functiondef); + if (type == "keyword c") return cont(maybeexpression); + if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator); + if (type == "operator") return cont(expression); + if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); + if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); + return cont(); + } + function maybeexpression(type) { + if (type.match(/[;\}\)\],]/)) return pass(); + return pass(expression); + } + + function maybeoperator(type, value) { + if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator); + if (type == "operator" || type == ":") return cont(expression); + if (type == ";") return; + if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); + if (type == ".") return cont(property, maybeoperator); + if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); + } + + function maybeattribute(type, value) { + if (type == "attribute") return cont(maybeattribute); + if (type == "function") return cont(functiondef); + if (type == "var") return cont(vardef1); + } + + function metadef(type, value) { + if(type == ":") return cont(metadef); + if(type == "variable") return cont(metadef); + if(type == "(") return cont(pushlex(")"), comasep(metaargs, ")"), poplex, statement); + } + function metaargs(type, value) { + if(typ == "variable") return cont(); + } + + function importdef (type, value) { + if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); } + else if(type == "variable" || type == "property" || type == ".") return cont(importdef); + } + + function typedef (type, value) + { + if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); } + } + + function maybelabel(type) { + if (type == ":") return cont(poplex, statement); + return pass(maybeoperator, expect(";"), poplex); + } + function property(type) { + if (type == "variable") {cx.marked = "property"; return cont();} + } + function objprop(type) { + if (type == "variable") cx.marked = "property"; + if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression); + } + function commasep(what, end) { + function proceed(type) { + if (type == ",") return cont(what, proceed); + if (type == end) return cont(); + return cont(expect(end)); + } + return function commaSeparated(type) { + if (type == end) return cont(); + else return pass(what, proceed); + }; + } + function block(type) { + if (type == "}") return cont(); + return pass(statement, block); + } + function vardef1(type, value) { + if (type == "variable"){register(value); return cont(typeuse, vardef2);} + return cont(); + } + function vardef2(type, value) { + if (value == "=") return cont(expression, vardef2); + if (type == ",") return cont(vardef1); + } + function forspec1(type, value) { + if (type == "variable") { + register(value); + } + return cont(pushlex(")"), pushcontext, forin, expression, poplex, statement, popcontext); + } + function forin(type, value) { + if (value == "in") return cont(); + } + function functiondef(type, value) { + if (type == "variable") {register(value); return cont(functiondef);} + if (value == "new") return cont(functiondef); + if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, typeuse, statement, popcontext); + } + function typeuse(type, value) { + if(type == ":") return cont(typestring); + } + function typestring(type, value) { + if(type == "type") return cont(); + if(type == "variable") return cont(); + if(type == "{") return cont(pushlex("}"), commasep(typeprop, "}"), poplex); + } + function typeprop(type, value) { + if(type == "variable") return cont(typeuse); + } + function funarg(type, value) { + if (type == "variable") {register(value); return cont(typeuse);} + } + + // Interface + + return { + startState: function(basecolumn) { + var defaulttypes = ["Int", "Float", "String", "Void", "Std", "Bool", "Dynamic", "Array"]; + return { + tokenize: haxeTokenBase, + reAllowed: true, + kwAllowed: true, + cc: [], + lexical: new HaxeLexical((basecolumn || 0) - indentUnit, 0, "block", false), + localVars: parserConfig.localVars, + importedtypes: defaulttypes, + context: parserConfig.localVars && {vars: parserConfig.localVars}, + indented: 0 + }; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = false; + state.indented = stream.indentation(); + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + if (type == "comment") return style; + state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/)); + state.kwAllowed = type != '.'; + return parseHaxe(state, style, type, content, stream); + }, + + indent: function(state, textAfter) { + if (state.tokenize != haxeTokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; + if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; + var type = lexical.type, closing = firstChar == type; + if (type == "vardef") return lexical.indented + 4; + else if (type == "form" && firstChar == "{") return lexical.indented; + else if (type == "stat" || type == "form") return lexical.indented + indentUnit; + else if (lexical.info == "switch" && !closing) + return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); + else if (lexical.align) return lexical.column + (closing ? 0 : 1); + else return lexical.indented + (closing ? 0 : indentUnit); + }, + + electricChars: "{}" + }; +}); + +CodeMirror.defineMIME("text/x-haxe", "haxe"); diff --git a/app/src/main/assets/mode/htmlembedded/htmlembedded.js b/app/src/main/assets/mode/htmlembedded/htmlembedded.js new file mode 100644 index 0000000000000000000000000000000000000000..b7888689f13115bed05694c7eb16d4059dbc8e66 --- /dev/null +++ b/app/src/main/assets/mode/htmlembedded/htmlembedded.js @@ -0,0 +1,73 @@ +CodeMirror.defineMode("htmlembedded", function(config, parserConfig) { + + //config settings + var scriptStartRegex = parserConfig.scriptStartRegex || /^<%/i, + scriptEndRegex = parserConfig.scriptEndRegex || /^%>/i; + + //inner modes + var scriptingMode, htmlMixedMode; + + //tokenizer when in html mode + function htmlDispatch(stream, state) { + if (stream.match(scriptStartRegex, false)) { + state.token=scriptingDispatch; + return scriptingMode.token(stream, state.scriptState); + } + else + return htmlMixedMode.token(stream, state.htmlState); + } + + //tokenizer when in scripting mode + function scriptingDispatch(stream, state) { + if (stream.match(scriptEndRegex, false)) { + state.token=htmlDispatch; + return htmlMixedMode.token(stream, state.htmlState); + } + else + return scriptingMode.token(stream, state.scriptState); + } + + + return { + startState: function() { + scriptingMode = scriptingMode || CodeMirror.getMode(config, parserConfig.scriptingModeSpec); + htmlMixedMode = htmlMixedMode || CodeMirror.getMode(config, "htmlmixed"); + return { + token : parserConfig.startOpen ? scriptingDispatch : htmlDispatch, + htmlState : htmlMixedMode.startState(), + scriptState : scriptingMode.startState() + }; + }, + + token: function(stream, state) { + return state.token(stream, state); + }, + + indent: function(state, textAfter) { + if (state.token == htmlDispatch) + return htmlMixedMode.indent(state.htmlState, textAfter); + else + return scriptingMode.indent(state.scriptState, textAfter); + }, + + copyState: function(state) { + return { + token : state.token, + htmlState : CodeMirror.copyState(htmlMixedMode, state.htmlState), + scriptState : CodeMirror.copyState(scriptingMode, state.scriptState) + }; + }, + + electricChars: "/{}:", + + innerMode: function(state) { + if (state.token == scriptingDispatch) return {state: state.scriptState, mode: scriptingMode}; + else return {state: state.htmlState, mode: htmlMixedMode}; + } + }; +}, "htmlmixed"); + +CodeMirror.defineMIME("application/x-ejs", { name: "htmlembedded", scriptingModeSpec:"javascript"}); +CodeMirror.defineMIME("application/x-aspx", { name: "htmlembedded", scriptingModeSpec:"text/x-csharp"}); +CodeMirror.defineMIME("application/x-jsp", { name: "htmlembedded", scriptingModeSpec:"text/x-java"}); +CodeMirror.defineMIME("application/x-erb", { name: "htmlembedded", scriptingModeSpec:"ruby"}); diff --git a/app/src/main/assets/mode/htmlmixed/htmlmixed.js b/app/src/main/assets/mode/htmlmixed/htmlmixed.js new file mode 100644 index 0000000000000000000000000000000000000000..46528482966cf1b18ed01877ce31dcc66260d4be --- /dev/null +++ b/app/src/main/assets/mode/htmlmixed/htmlmixed.js @@ -0,0 +1,84 @@ +CodeMirror.defineMode("htmlmixed", function(config) { + var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true}); + var jsMode = CodeMirror.getMode(config, "javascript"); + var cssMode = CodeMirror.getMode(config, "css"); + + function html(stream, state) { + var style = htmlMode.token(stream, state.htmlState); + if (style == "tag" && stream.current() == ">" && state.htmlState.context) { + if (/^script$/i.test(state.htmlState.context.tagName)) { + state.token = javascript; + state.localState = jsMode.startState(htmlMode.indent(state.htmlState, "")); + } + else if (/^style$/i.test(state.htmlState.context.tagName)) { + state.token = css; + state.localState = cssMode.startState(htmlMode.indent(state.htmlState, "")); + } + } + return style; + } + function maybeBackup(stream, pat, style) { + var cur = stream.current(); + var close = cur.search(pat), m; + if (close > -1) stream.backUp(cur.length - close); + else if (m = cur.match(/<\/?$/)) { + stream.backUp(cur[0].length); + if (!stream.match(pat, false)) stream.match(cur[0]); + } + return style; + } + function javascript(stream, state) { + if (stream.match(/^<\/\s*script\s*>/i, false)) { + state.token = html; + state.localState = null; + return html(stream, state); + } + return maybeBackup(stream, /<\/\s*script\s*>/, + jsMode.token(stream, state.localState)); + } + function css(stream, state) { + if (stream.match(/^<\/\s*style\s*>/i, false)) { + state.token = html; + state.localState = null; + return html(stream, state); + } + return maybeBackup(stream, /<\/\s*style\s*>/, + cssMode.token(stream, state.localState)); + } + + return { + startState: function() { + var state = htmlMode.startState(); + return {token: html, localState: null, mode: "html", htmlState: state}; + }, + + copyState: function(state) { + if (state.localState) + var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState); + return {token: state.token, localState: local, mode: state.mode, + htmlState: CodeMirror.copyState(htmlMode, state.htmlState)}; + }, + + token: function(stream, state) { + return state.token(stream, state); + }, + + indent: function(state, textAfter) { + if (state.token == html || /^\s*<\//.test(textAfter)) + return htmlMode.indent(state.htmlState, textAfter); + else if (state.token == javascript) + return jsMode.indent(state.localState, textAfter); + else + return cssMode.indent(state.localState, textAfter); + }, + + electricChars: "/{}:", + + innerMode: function(state) { + var mode = state.token == html ? htmlMode : state.token == javascript ? jsMode : cssMode; + return {state: state.localState || state.htmlState, mode: mode}; + } + }; +}, "xml", "javascript", "css"); + +CodeMirror.defineMIME("text/html", "htmlmixed"); diff --git a/app/src/main/assets/mode/javascript/javascript.js b/app/src/main/assets/mode/javascript/javascript.js new file mode 100644 index 0000000000000000000000000000000000000000..37f6f8737a66f89063089fe124522309aaee02f8 --- /dev/null +++ b/app/src/main/assets/mode/javascript/javascript.js @@ -0,0 +1,409 @@ +// TODO actually recognize syntax of TypeScript constructs + +CodeMirror.defineMode("javascript", function(config, parserConfig) { + var indentUnit = config.indentUnit; + var jsonMode = parserConfig.json; + var isTS = parserConfig.typescript; + + // Tokenizer + + var keywords = function(){ + function kw(type) {return {type: type, style: "keyword"};} + var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); + var operator = kw("operator"), atom = {type: "atom", style: "atom"}; + + var jsKeywords = { + "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, + "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, + "var": kw("var"), "const": kw("var"), "let": kw("var"), + "function": kw("function"), "catch": kw("catch"), + "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), + "in": operator, "typeof": operator, "instanceof": operator, + "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom + }; + + // Extend the 'normal' keywords with the TypeScript language extensions + if (isTS) { + var type = {type: "variable", style: "variable-3"}; + var tsKeywords = { + // object-like things + "interface": kw("interface"), + "class": kw("class"), + "extends": kw("extends"), + "constructor": kw("constructor"), + + // scope modifiers + "public": kw("public"), + "private": kw("private"), + "protected": kw("protected"), + "static": kw("static"), + + "super": kw("super"), + + // types + "string": type, "number": type, "bool": type, "any": type + }; + + for (var attr in tsKeywords) { + jsKeywords[attr] = tsKeywords[attr]; + } + } + + return jsKeywords; + }(); + + var isOperatorChar = /[+\-*&%=<>!?|]/; + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + + function nextUntilUnescaped(stream, end) { + var escaped = false, next; + while ((next = stream.next()) != null) { + if (next == end && !escaped) + return false; + escaped = !escaped && next == "\\"; + } + return escaped; + } + + // Used as scratch variables to communicate multiple values without + // consing up tons of objects. + var type, content; + function ret(tp, style, cont) { + type = tp; content = cont; + return style; + } + + function jsTokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"' || ch == "'") + return chain(stream, state, jsTokenString(ch)); + else if (/[\[\]{}\(\),;\:\.]/.test(ch)) + return ret(ch); + else if (ch == "0" && stream.eat(/x/i)) { + stream.eatWhile(/[\da-f]/i); + return ret("number", "number"); + } + else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) { + stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); + return ret("number", "number"); + } + else if (ch == "/") { + if (stream.eat("*")) { + return chain(stream, state, jsTokenComment); + } + else if (stream.eat("/")) { + stream.skipToEnd(); + return ret("comment", "comment"); + } + else if (state.lastType == "operator" || state.lastType == "keyword c" || + /^[\[{}\(,;:]$/.test(state.lastType)) { + nextUntilUnescaped(stream, "/"); + stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla + return ret("regexp", "string-2"); + } + else { + stream.eatWhile(isOperatorChar); + return ret("operator", null, stream.current()); + } + } + else if (ch == "#") { + stream.skipToEnd(); + return ret("error", "error"); + } + else if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return ret("operator", null, stream.current()); + } + else { + stream.eatWhile(/[\w\$_]/); + var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; + return (known && state.lastType != ".") ? ret(known.type, known.style, word) : + ret("variable", "variable", word); + } + } + + function jsTokenString(quote) { + return function(stream, state) { + if (!nextUntilUnescaped(stream, quote)) + state.tokenize = jsTokenBase; + return ret("string", "string"); + }; + } + + function jsTokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = jsTokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return ret("comment", "comment"); + } + + // Parser + + var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; + + function JSLexical(indented, column, type, align, prev, info) { + this.indented = indented; + this.column = column; + this.type = type; + this.prev = prev; + this.info = info; + if (align != null) this.align = align; + } + + function inScope(state, varname) { + for (var v = state.localVars; v; v = v.next) + if (v.name == varname) return true; + } + + function parseJS(state, style, type, content, stream) { + var cc = state.cc; + // Communicate our context to the combinators. + // (Less wasteful than consing up a hundred closures on every call.) + cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; + + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = true; + + while(true) { + var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; + if (combinator(type, content)) { + while(cc.length && cc[cc.length - 1].lex) + cc.pop()(); + if (cx.marked) return cx.marked; + if (type == "variable" && inScope(state, content)) return "variable-2"; + return style; + } + } + } + + // Combinator utils + + var cx = {state: null, column: null, marked: null, cc: null}; + function pass() { + for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); + } + function cont() { + pass.apply(null, arguments); + return true; + } + function register(varname) { + var state = cx.state; + if (state.context) { + cx.marked = "def"; + for (var v = state.localVars; v; v = v.next) + if (v.name == varname) return; + state.localVars = {name: varname, next: state.localVars}; + } + } + + // Combinators + + var defaultVars = {name: "this", next: {name: "arguments"}}; + function pushcontext() { + cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; + cx.state.localVars = defaultVars; + } + function popcontext() { + cx.state.localVars = cx.state.context.vars; + cx.state.context = cx.state.context.prev; + } + function pushlex(type, info) { + var result = function() { + var state = cx.state; + state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info); + }; + result.lex = true; + return result; + } + function poplex() { + var state = cx.state; + if (state.lexical.prev) { + if (state.lexical.type == ")") + state.indented = state.lexical.indented; + state.lexical = state.lexical.prev; + } + } + poplex.lex = true; + + function expect(wanted) { + return function expecting(type) { + if (type == wanted) return cont(); + else if (wanted == ";") return pass(); + else return cont(arguments.callee); + }; + } + + function statement(type) { + if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); + if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); + if (type == "keyword b") return cont(pushlex("form"), statement, poplex); + if (type == "{") return cont(pushlex("}"), block, poplex); + if (type == ";") return cont(); + if (type == "function") return cont(functiondef); + if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), + poplex, statement, poplex); + if (type == "variable") return cont(pushlex("stat"), maybelabel); + if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), + block, poplex, poplex); + if (type == "case") return cont(expression, expect(":")); + if (type == "default") return cont(expect(":")); + if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), + statement, poplex, popcontext); + return pass(pushlex("stat"), expression, expect(";"), poplex); + } + function expression(type) { + if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator); + if (type == "function") return cont(functiondef); + if (type == "keyword c") return cont(maybeexpression); + if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator); + if (type == "operator") return cont(expression); + if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); + if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); + return cont(); + } + function maybeexpression(type) { + if (type.match(/[;\}\)\],]/)) return pass(); + return pass(expression); + } + + function maybeoperator(type, value) { + if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator); + if (type == "operator" && value == "?") return cont(expression, expect(":"), expression); + if (type == ";") return; + if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); + if (type == ".") return cont(property, maybeoperator); + if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); + } + function maybelabel(type) { + if (type == ":") return cont(poplex, statement); + return pass(maybeoperator, expect(";"), poplex); + } + function property(type) { + if (type == "variable") {cx.marked = "property"; return cont();} + } + function objprop(type) { + if (type == "variable") cx.marked = "property"; + if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression); + } + function commasep(what, end) { + function proceed(type) { + if (type == ",") return cont(what, proceed); + if (type == end) return cont(); + return cont(expect(end)); + } + return function commaSeparated(type) { + if (type == end) return cont(); + else return pass(what, proceed); + }; + } + function block(type) { + if (type == "}") return cont(); + return pass(statement, block); + } + function maybetype(type) { + if (type == ":") return cont(typedef); + return pass(); + } + function typedef(type) { + if (type == "variable"){cx.marked = "variable-3"; return cont();} + return pass(); + } + function vardef1(type, value) { + if (type == "variable") { + register(value); + return isTS ? cont(maybetype, vardef2) : cont(vardef2); + } + return pass(); + } + function vardef2(type, value) { + if (value == "=") return cont(expression, vardef2); + if (type == ",") return cont(vardef1); + } + function forspec1(type) { + if (type == "var") return cont(vardef1, expect(";"), forspec2); + if (type == ";") return cont(forspec2); + if (type == "variable") return cont(formaybein); + return cont(forspec2); + } + function formaybein(type, value) { + if (value == "in") return cont(expression); + return cont(maybeoperator, forspec2); + } + function forspec2(type, value) { + if (type == ";") return cont(forspec3); + if (value == "in") return cont(expression); + return cont(expression, expect(";"), forspec3); + } + function forspec3(type) { + if (type != ")") cont(expression); + } + function functiondef(type, value) { + if (type == "variable") {register(value); return cont(functiondef);} + if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); + } + function funarg(type, value) { + if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();} + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: jsTokenBase, + lastType: null, + cc: [], + lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), + localVars: parserConfig.localVars, + context: parserConfig.localVars && {vars: parserConfig.localVars}, + indented: 0 + }; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = false; + state.indented = stream.indentation(); + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + if (type == "comment") return style; + state.lastType = type; + return parseJS(state, style, type, content, stream); + }, + + indent: function(state, textAfter) { + if (state.tokenize == jsTokenComment) return CodeMirror.Pass; + if (state.tokenize != jsTokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; + if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; + var type = lexical.type, closing = firstChar == type; + if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0); + else if (type == "form" && firstChar == "{") return lexical.indented; + else if (type == "form") return lexical.indented + indentUnit; + else if (type == "stat") + return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? indentUnit : 0); + else if (lexical.info == "switch" && !closing) + return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); + else if (lexical.align) return lexical.column + (closing ? 0 : 1); + else return lexical.indented + (closing ? 0 : indentUnit); + }, + + electricChars: ":{}" + }; +}); + +CodeMirror.defineMIME("text/javascript", "javascript"); +CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); +CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); +CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); diff --git a/app/src/main/assets/mode/jinja2/jinja2.js b/app/src/main/assets/mode/jinja2/jinja2.js new file mode 100644 index 0000000000000000000000000000000000000000..75419d84687ddbc59d4f9667ce626f458dc7e59e --- /dev/null +++ b/app/src/main/assets/mode/jinja2/jinja2.js @@ -0,0 +1,42 @@ +CodeMirror.defineMode("jinja2", function(config, parserConf) { + var keywords = ["block", "endblock", "for", "endfor", "in", "true", "false", + "loop", "none", "self", "super", "if", "as", "not", "and", + "else", "import", "with", "without", "context"]; + keywords = new RegExp("^((" + keywords.join(")|(") + "))\\b"); + + function tokenBase (stream, state) { + var ch = stream.next(); + if (ch == "{") { + if (ch = stream.eat(/\{|%|#/)) { + stream.eat("-"); + state.tokenize = inTag(ch); + return "tag"; + } + } + } + function inTag (close) { + if (close == "{") { + close = "}"; + } + return function (stream, state) { + var ch = stream.next(); + if ((ch == close || (ch == "-" && stream.eat(close))) + && stream.eat("}")) { + state.tokenize = tokenBase; + return "tag"; + } + if (stream.match(keywords)) { + return "keyword"; + } + return close == "#" ? "comment" : "string"; + }; + } + return { + startState: function () { + return {tokenize: tokenBase}; + }, + token: function (stream, state) { + return state.tokenize(stream, state); + } + }; +}); diff --git a/app/src/main/assets/mode/less/less.js b/app/src/main/assets/mode/less/less.js new file mode 100644 index 0000000000000000000000000000000000000000..70cd5c937b3fbc208c8aacbb6fd8d81b33795bf5 --- /dev/null +++ b/app/src/main/assets/mode/less/less.js @@ -0,0 +1,266 @@ +/* + LESS mode - http://www.lesscss.org/ + Ported to CodeMirror by Peter Kroon + Report bugs/issues here: https://github.com/marijnh/CodeMirror/issues GitHub: @peterkroon +*/ + +CodeMirror.defineMode("less", function(config) { + var indentUnit = config.indentUnit, type; + function ret(style, tp) {type = tp; return style;} + //html tags + var tags = "a abbr acronym address applet area article aside audio b base basefont bdi bdo big blockquote body br button canvas caption cite code col colgroup command datalist dd del details dfn dir div dl dt em embed fieldset figcaption figure font footer form frame frameset h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins keygen kbd label legend li link map mark menu meta meter nav noframes noscript object ol optgroup option output p param pre progress q rp rt ruby s samp script section select small source span strike strong style sub summary sup table tbody td textarea tfoot th thead time title tr track tt u ul var video wbr".split(' '); + + function inTagsArray(val){ + for(var i=0; i*\/]/.test(ch)) { + if(stream.peek() == "=" || type == "a")return ret("string", "string"); + return ret(null, "select-op"); + } + else if (/[;{}:\[\]()~\|]/.test(ch)) { + if(ch == ":"){ + stream.eatWhile(/[a-z\\\-]/); + if( selectors.test(stream.current()) ){ + return ret("tag", "tag"); + }else if(stream.peek() == ":"){//::-webkit-search-decoration + stream.next(); + stream.eatWhile(/[a-z\\\-]/); + if(stream.current().match(/\:\:\-(o|ms|moz|webkit)\-/))return ret("string", "string"); + if( selectors.test(stream.current().substring(1)) )return ret("tag", "tag"); + return ret(null, ch); + }else{ + return ret(null, ch); + } + }else if(ch == "~"){ + if(type == "r")return ret("string", "string"); + }else{ + return ret(null, ch); + } + } + else if (ch == ".") { + if(type == "(" || type == "string")return ret("string", "string"); // allow url(../image.png) + stream.eatWhile(/[\a-zA-Z0-9\-_]/); + if(stream.peek() == " ")stream.eatSpace(); + if(stream.peek() == ")")return ret("number", "unit");//rgba(0,0,0,.25); + return ret("tag", "tag"); + } + else if (ch == "#") { + //we don't eat white-space, we want the hex color and or id only + stream.eatWhile(/[A-Za-z0-9]/); + //check if there is a proper hex color length e.g. #eee || #eeeEEE + if(stream.current().length == 4 || stream.current().length == 7){ + if(stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false) != null){//is there a valid hex color value present in the current stream + //when not a valid hex value, parse as id + if(stream.current().substring(1) != stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false))return ret("atom", "tag"); + //eat white-space + stream.eatSpace(); + //when hex value declaration doesn't end with [;,] but is does with a slash/cc comment treat it as an id, just like the other hex values that don't end with[;,] + if( /[\/<>.(){!$%^&*_\-\\?=+\|#'~`]/.test(stream.peek()) )return ret("atom", "tag"); + //#time { color: #aaa } + else if(stream.peek() == "}" )return ret("number", "unit"); + //we have a valid hex color value, parse as id whenever an element/class is defined after the hex(id) value e.g. #eee aaa || #eee .aaa + else if( /[a-zA-Z\\]/.test(stream.peek()) )return ret("atom", "tag"); + //when a hex value is on the end of a line, parse as id + else if(stream.eol())return ret("atom", "tag"); + //default + else return ret("number", "unit"); + }else{//when not a valid hexvalue in the current stream e.g. #footer + stream.eatWhile(/[\w\\\-]/); + return ret("atom", "tag"); + } + }else{//when not a valid hexvalue length + stream.eatWhile(/[\w\\\-]/); + return ret("atom", "tag"); + } + } + else if (ch == "&") { + stream.eatWhile(/[\w\-]/); + return ret(null, ch); + } + else { + stream.eatWhile(/[\w\\\-_%.{]/); + if(type == "string"){ + return ret("string", "string"); + }else if(stream.current().match(/(^http$|^https$)/) != null){ + stream.eatWhile(/[\w\\\-_%.{:\/]/); + return ret("string", "string"); + }else if(stream.peek() == "<" || stream.peek() == ">"){ + return ret("tag", "tag"); + }else if( /\(/.test(stream.peek()) ){ + return ret(null, ch); + }else if (stream.peek() == "/" && state.stack[state.stack.length-1] != undefined){ // url(dir/center/image.png) + return ret("string", "string"); + }else if( stream.current().match(/\-\d|\-.\d/) ){ // match e.g.: -5px -0.4 etc... only colorize the minus sign + //commment out these 2 comment if you want the minus sign to be parsed as null -500px + //stream.backUp(stream.current().length-1); + //return ret(null, ch); //console.log( stream.current() ); + return ret("number", "unit"); + }else if( inTagsArray(stream.current().toLowerCase()) ){ // match html tags + return ret("tag", "tag"); + }else if( /\/|[\s\)]/.test(stream.peek() || stream.eol() || (stream.eatSpace() && stream.peek() == "/")) && stream.current().indexOf(".") !== -1){ + if(stream.current().substring(stream.current().length-1,stream.current().length) == "{"){ + stream.backUp(1); + return ret("tag", "tag"); + }//end if + stream.eatSpace(); + if( /[{<>.a-zA-Z\/]/.test(stream.peek()) || stream.eol() )return ret("tag", "tag"); // e.g. button.icon-plus + return ret("string", "string"); // let url(/images/logo.png) without quotes return as string + }else if( stream.eol() || stream.peek() == "[" || stream.peek() == "#" || type == "tag" ){ + if(stream.current().substring(stream.current().length-1,stream.current().length) == "{")stream.backUp(1); + return ret("tag", "tag"); + }else if(type == "compare" || type == "a" || type == "("){ + return ret("string", "string"); + }else if(type == "|" || stream.current() == "-" || type == "["){ + return ret(null, ch); + }else if(stream.peek() == ":") { + stream.next(); + var t_v = stream.peek() == ":" ? true : false; + if(!t_v){ + var old_pos = stream.pos; + var sc = stream.current().length; + stream.eatWhile(/[a-z\\\-]/); + var new_pos = stream.pos; + if(stream.current().substring(sc-1).match(selectors) != null){ + stream.backUp(new_pos-(old_pos-1)); + return ret("tag", "tag"); + } else stream.backUp(new_pos-(old_pos-1)); + }else{ + stream.backUp(1); + } + if(t_v)return ret("tag", "tag"); else return ret("variable", "variable"); + }else{ + return ret("variable", "variable"); + } + } + } + + function tokenSComment(stream, state) { // SComment = Slash comment + stream.skipToEnd(); + state.tokenize = tokenBase; + return ret("comment", "comment"); + } + + function tokenCComment(stream, state) { + var maybeEnd = false, ch; + while ((ch = stream.next()) != null) { + if (maybeEnd && ch == "/") { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return ret("comment", "comment"); + } + + function tokenSGMLComment(stream, state) { + var dashes = 0, ch; + while ((ch = stream.next()) != null) { + if (dashes >= 2 && ch == ">") { + state.tokenize = tokenBase; + break; + } + dashes = (ch == "-") ? dashes + 1 : 0; + } + return ret("comment", "comment"); + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) + break; + escaped = !escaped && ch == "\\"; + } + if (!escaped) state.tokenize = tokenBase; + return ret("string", "string"); + }; + } + + return { + startState: function(base) { + return {tokenize: tokenBase, + baseIndent: base || 0, + stack: []}; + }, + + token: function(stream, state) { + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + + var context = state.stack[state.stack.length-1]; + if (type == "hash" && context == "rule") style = "atom"; + else if (style == "variable") { + if (context == "rule") style = null; //"tag" + else if (!context || context == "@media{") { + style = stream.current() == "when" ? "variable" : + /[\s,|\s\)|\s]/.test(stream.peek()) ? "tag" : type; + } + } + + if (context == "rule" && /^[\{\};]$/.test(type)) + state.stack.pop(); + if (type == "{") { + if (context == "@media") state.stack[state.stack.length-1] = "@media{"; + else state.stack.push("{"); + } + else if (type == "}") state.stack.pop(); + else if (type == "@media") state.stack.push("@media"); + else if (context == "{" && type != "comment") state.stack.push("rule"); + return style; + }, + + indent: function(state, textAfter) { + var n = state.stack.length; + if (/^\}/.test(textAfter)) + n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1; + return state.baseIndent + n * indentUnit; + }, + + electricChars: "}" + }; +}); + +CodeMirror.defineMIME("text/x-less", "less"); +if (!CodeMirror.mimeModes.hasOwnProperty("text/css")) + CodeMirror.defineMIME("text/css", "less"); \ No newline at end of file diff --git a/app/src/main/assets/mode/lua/lua.js b/app/src/main/assets/mode/lua/lua.js new file mode 100644 index 0000000000000000000000000000000000000000..97fb2c6f96dfcf896131106b490524874bc6a507 --- /dev/null +++ b/app/src/main/assets/mode/lua/lua.js @@ -0,0 +1,140 @@ +// LUA mode. Ported to CodeMirror 2 from Franciszek Wawrzak's +// CodeMirror 1 mode. +// highlights keywords, strings, comments (no leveling supported! ("[==[")), tokens, basic indenting + +CodeMirror.defineMode("lua", function(config, parserConfig) { + var indentUnit = config.indentUnit; + + function prefixRE(words) { + return new RegExp("^(?:" + words.join("|") + ")", "i"); + } + function wordRE(words) { + return new RegExp("^(?:" + words.join("|") + ")$", "i"); + } + var specials = wordRE(parserConfig.specials || []); + + // long list of standard functions from lua manual + var builtins = wordRE([ + "_G","_VERSION","assert","collectgarbage","dofile","error","getfenv","getmetatable","ipairs","load", + "loadfile","loadstring","module","next","pairs","pcall","print","rawequal","rawget","rawset","require", + "select","setfenv","setmetatable","tonumber","tostring","type","unpack","xpcall", + + "coroutine.create","coroutine.resume","coroutine.running","coroutine.status","coroutine.wrap","coroutine.yield", + + "debug.debug","debug.getfenv","debug.gethook","debug.getinfo","debug.getlocal","debug.getmetatable", + "debug.getregistry","debug.getupvalue","debug.setfenv","debug.sethook","debug.setlocal","debug.setmetatable", + "debug.setupvalue","debug.traceback", + + "close","flush","lines","read","seek","setvbuf","write", + + "io.close","io.flush","io.input","io.lines","io.open","io.output","io.popen","io.read","io.stderr","io.stdin", + "io.stdout","io.tmpfile","io.type","io.write", + + "math.abs","math.acos","math.asin","math.atan","math.atan2","math.ceil","math.cos","math.cosh","math.deg", + "math.exp","math.floor","math.fmod","math.frexp","math.huge","math.ldexp","math.log","math.log10","math.max", + "math.min","math.modf","math.pi","math.pow","math.rad","math.random","math.randomseed","math.sin","math.sinh", + "math.sqrt","math.tan","math.tanh", + + "os.clock","os.date","os.difftime","os.execute","os.exit","os.getenv","os.remove","os.rename","os.setlocale", + "os.time","os.tmpname", + + "package.cpath","package.loaded","package.loaders","package.loadlib","package.path","package.preload", + "package.seeall", + + "string.byte","string.char","string.dump","string.find","string.format","string.gmatch","string.gsub", + "string.len","string.lower","string.match","string.rep","string.reverse","string.sub","string.upper", + + "table.concat","table.insert","table.maxn","table.remove","table.sort" + ]); + var keywords = wordRE(["and","break","elseif","false","nil","not","or","return", + "true","function", "end", "if", "then", "else", "do", + "while", "repeat", "until", "for", "in", "local" ]); + + var indentTokens = wordRE(["function", "if","repeat","do", "\\(", "{"]); + var dedentTokens = wordRE(["end", "until", "\\)", "}"]); + var dedentPartial = prefixRE(["end", "until", "\\)", "}", "else", "elseif"]); + + function readBracket(stream) { + var level = 0; + while (stream.eat("=")) ++level; + stream.eat("["); + return level; + } + + function normal(stream, state) { + var ch = stream.next(); + if (ch == "-" && stream.eat("-")) { + if (stream.eat("[") && stream.eat("[")) + return (state.cur = bracketed(readBracket(stream), "comment"))(stream, state); + stream.skipToEnd(); + return "comment"; + } + if (ch == "\"" || ch == "'") + return (state.cur = string(ch))(stream, state); + if (ch == "[" && /[\[=]/.test(stream.peek())) + return (state.cur = bracketed(readBracket(stream), "string"))(stream, state); + if (/\d/.test(ch)) { + stream.eatWhile(/[\w.%]/); + return "number"; + } + if (/[\w_]/.test(ch)) { + stream.eatWhile(/[\w\\\-_.]/); + return "variable"; + } + return null; + } + + function bracketed(level, style) { + return function(stream, state) { + var curlev = null, ch; + while ((ch = stream.next()) != null) { + if (curlev == null) {if (ch == "]") curlev = 0;} + else if (ch == "=") ++curlev; + else if (ch == "]" && curlev == level) { state.cur = normal; break; } + else curlev = null; + } + return style; + }; + } + + function string(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) break; + escaped = !escaped && ch == "\\"; + } + if (!escaped) state.cur = normal; + return "string"; + }; + } + + return { + startState: function(basecol) { + return {basecol: basecol || 0, indentDepth: 0, cur: normal}; + }, + + token: function(stream, state) { + if (stream.eatSpace()) return null; + var style = state.cur(stream, state); + var word = stream.current(); + if (style == "variable") { + if (keywords.test(word)) style = "keyword"; + else if (builtins.test(word)) style = "builtin"; + else if (specials.test(word)) style = "variable-2"; + } + if ((style != "comment") && (style != "string")){ + if (indentTokens.test(word)) ++state.indentDepth; + else if (dedentTokens.test(word)) --state.indentDepth; + } + return style; + }, + + indent: function(state, textAfter) { + var closing = dedentPartial.test(textAfter); + return state.basecol + indentUnit * (state.indentDepth - (closing ? 1 : 0)); + } + }; +}); + +CodeMirror.defineMIME("text/x-lua", "lua"); diff --git a/app/src/main/assets/mode/markdown/markdown.js b/app/src/main/assets/mode/markdown/markdown.js new file mode 100644 index 0000000000000000000000000000000000000000..d227fc9b9196ae25b76bac094496625208266c47 --- /dev/null +++ b/app/src/main/assets/mode/markdown/markdown.js @@ -0,0 +1,481 @@ +CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { + + var htmlFound = CodeMirror.mimeModes.hasOwnProperty("text/html"); + var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? "text/html" : "text/plain"); + var aliases = { + html: "htmlmixed", + js: "javascript", + json: "application/json", + c: "text/x-csrc", + "c++": "text/x-c++src", + java: "text/x-java", + csharp: "text/x-csharp", + "c#": "text/x-csharp" + }; + + var getMode = (function () { + var i, modes = {}, mimes = {}, mime; + + var list = CodeMirror.listModes(); + for (i = 0; i < list.length; i++) { + modes[list[i]] = list[i]; + } + var mimesList = CodeMirror.listMIMEs(); + for (i = 0; i < mimesList.length; i++) { + mime = mimesList[i].mime; + mimes[mime] = mimesList[i].mime; + } + + for (var a in aliases) { + if (aliases[a] in modes || aliases[a] in mimes) + modes[a] = aliases[a]; + } + + return function (lang) { + return modes[lang] ? CodeMirror.getMode(cmCfg, modes[lang]) : null; + }; + }()); + + // Should underscores in words open/close em/strong? + if (modeCfg.underscoresBreakWords === undefined) + modeCfg.underscoresBreakWords = true; + + // Turn on fenced code blocks? ("```" to start/end) + if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false; + + var codeDepth = 0; + var prevLineHasContent = false + , thisLineHasContent = false; + + var header = 'header' + , code = 'comment' + , quote = 'quote' + , list = 'string' + , hr = 'hr' + , image = 'tag' + , linkinline = 'link' + , linkemail = 'link' + , linktext = 'link' + , linkhref = 'string' + , em = 'em' + , strong = 'strong' + , emstrong = 'emstrong'; + + var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/ + , ulRE = /^[*\-+]\s+/ + , olRE = /^[0-9]+\.\s+/ + , headerRE = /^(?:\={1,}|-{1,})$/ + , textRE = /^[^!\[\]*_\\<>` "'(]+/; + + function switchInline(stream, state, f) { + state.f = state.inline = f; + return f(stream, state); + } + + function switchBlock(stream, state, f) { + state.f = state.block = f; + return f(stream, state); + } + + + // Blocks + + function blankLine(state) { + // Reset linkTitle state + state.linkTitle = false; + // Reset EM state + state.em = false; + // Reset STRONG state + state.strong = false; + // Reset state.quote + state.quote = false; + if (!htmlFound && state.f == htmlBlock) { + state.f = inlineNormal; + state.block = blockNormal; + } + return null; + } + + function blockNormal(stream, state) { + + if (state.list !== false && state.indentationDiff >= 0) { // Continued list + if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block + state.indentation -= state.indentationDiff; + } + state.list = null; + } else { // No longer a list + state.list = false; + } + + if (state.indentationDiff >= 4) { + state.indentation -= 4; + stream.skipToEnd(); + return code; + } else if (stream.eatSpace()) { + return null; + } else if (stream.peek() === '#' || (prevLineHasContent && stream.match(headerRE)) ) { + state.header = true; + } else if (stream.eat('>')) { + state.indentation++; + state.quote = true; + } else if (stream.peek() === '[') { + return switchInline(stream, state, footnoteLink); + } else if (stream.match(hrRE, true)) { + return hr; + } else if (stream.match(ulRE, true) || stream.match(olRE, true)) { + state.indentation += 4; + state.list = true; + } else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\w+#]*)/, true)) { + // try switching mode + state.localMode = getMode(RegExp.$1); + if (state.localMode) state.localState = state.localMode.startState(); + switchBlock(stream, state, local); + return code; + } + + return switchInline(stream, state, state.inline); + } + + function htmlBlock(stream, state) { + var style = htmlMode.token(stream, state.htmlState); + if (htmlFound && style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) { + state.f = inlineNormal; + state.block = blockNormal; + } + if (state.md_inside && stream.current().indexOf(">")!=-1) { + state.f = inlineNormal; + state.block = blockNormal; + state.htmlState.context = undefined; + } + return style; + } + + function local(stream, state) { + if (stream.sol() && stream.match(/^```/, true)) { + state.localMode = state.localState = null; + state.f = inlineNormal; + state.block = blockNormal; + return code; + } else if (state.localMode) { + return state.localMode.token(stream, state.localState); + } else { + stream.skipToEnd(); + return code; + } + } + + function codeBlock(stream, state) { + if(stream.match(codeBlockRE, true)){ + state.f = inlineNormal; + state.block = blockNormal; + switchInline(stream, state, state.inline); + return code; + } + stream.skipToEnd(); + return code; + } + + // Inline + function getType(state) { + var styles = []; + + if (state.strong) { styles.push(state.em ? emstrong : strong); } + else if (state.em) { styles.push(em); } + + if (state.linkText) { styles.push(linktext); } + + if (state.code) { styles.push(code); } + + if (state.header) { styles.push(header); } + if (state.quote) { styles.push(quote); } + if (state.list !== false) { styles.push(list); } + + return styles.length ? styles.join(' ') : null; + } + + function handleText(stream, state) { + if (stream.match(textRE, true)) { + return getType(state); + } + return undefined; + } + + function inlineNormal(stream, state) { + var style = state.text(stream, state); + if (typeof style !== 'undefined') + return style; + + if (state.list) { // List marker (*, +, -, 1., etc) + state.list = null; + return list; + } + + var ch = stream.next(); + + if (ch === '\\') { + stream.next(); + return getType(state); + } + + // Matches link titles present on next line + if (state.linkTitle) { + state.linkTitle = false; + var matchCh = ch; + if (ch === '(') { + matchCh = ')'; + } + matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); + var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh; + if (stream.match(new RegExp(regex), true)) { + return linkhref; + } + } + + // If this block is changed, it may need to be updated in GFM mode + if (ch === '`') { + var t = getType(state); + var before = stream.pos; + stream.eatWhile('`'); + var difference = 1 + stream.pos - before; + if (!state.code) { + codeDepth = difference; + state.code = true; + return getType(state); + } else { + if (difference === codeDepth) { // Must be exact + state.code = false; + return t; + } + return getType(state); + } + } else if (state.code) { + return getType(state); + } + + if (ch === '!' && stream.match(/\[.*\] ?(?:\(|\[)/, false)) { + stream.match(/\[.*\]/); + state.inline = state.f = linkHref; + return image; + } + + if (ch === '[' && stream.match(/.*\](\(| ?\[)/, false)) { + state.linkText = true; + return getType(state); + } + + if (ch === ']' && state.linkText) { + var type = getType(state); + state.linkText = false; + state.inline = state.f = linkHref; + return type; + } + + if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, true)) { + return switchInline(stream, state, inlineElement(linkinline, '>')); + } + + if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, true)) { + return switchInline(stream, state, inlineElement(linkemail, '>')); + } + + if (ch === '<' && stream.match(/^\w/, false)) { + var md_inside = false; + if (stream.string.indexOf(">")!=-1) { + var atts = stream.string.substring(1,stream.string.indexOf(">")); + if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) { + state.md_inside = true; + } + } + stream.backUp(1); + return switchBlock(stream, state, htmlBlock); + } + + if (ch === '<' && stream.match(/^\/\w*?>/)) { + state.md_inside = false; + return "tag"; + } + + var ignoreUnderscore = false; + if (!modeCfg.underscoresBreakWords) { + if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) { + var prevPos = stream.pos - 2; + if (prevPos >= 0) { + var prevCh = stream.string.charAt(prevPos); + if (prevCh !== '_' && prevCh.match(/(\w)/, false)) { + ignoreUnderscore = true; + } + } + } + } + var t = getType(state); + if (ch === '*' || (ch === '_' && !ignoreUnderscore)) { + if (state.strong === ch && stream.eat(ch)) { // Remove STRONG + state.strong = false; + return t; + } else if (!state.strong && stream.eat(ch)) { // Add STRONG + state.strong = ch; + return getType(state); + } else if (state.em === ch) { // Remove EM + state.em = false; + return t; + } else if (!state.em) { // Add EM + state.em = ch; + return getType(state); + } + } else if (ch === ' ') { + if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces + if (stream.peek() === ' ') { // Surrounded by spaces, ignore + return getType(state); + } else { // Not surrounded by spaces, back up pointer + stream.backUp(1); + } + } + } + + return getType(state); + } + + function linkHref(stream, state) { + // Check if space, and return NULL if so (to avoid marking the space) + if(stream.eatSpace()){ + return null; + } + var ch = stream.next(); + if (ch === '(' || ch === '[') { + return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']')); + } + return 'error'; + } + + function footnoteLink(stream, state) { + if (stream.match(/^[^\]]*\]:/, true)) { + state.f = footnoteUrl; + return linktext; + } + return switchInline(stream, state, inlineNormal); + } + + function footnoteUrl(stream, state) { + // Check if space, and return NULL if so (to avoid marking the space) + if(stream.eatSpace()){ + return null; + } + // Match URL + stream.match(/^[^\s]+/, true); + // Check for link title + if (stream.peek() === undefined) { // End of line, set flag to check next line + state.linkTitle = true; + } else { // More content on line, check if link title + stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); + } + state.f = state.inline = inlineNormal; + return linkhref; + } + + var savedInlineRE = []; + function inlineRE(endChar) { + if (!savedInlineRE[endChar]) { + // Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741) + endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); + // Match any non-endChar, escaped character, as well as the closing + // endChar. + savedInlineRE[endChar] = new RegExp('^(?:[^\\\\]|\\\\.)*?(' + endChar + ')'); + } + return savedInlineRE[endChar]; + } + + function inlineElement(type, endChar, next) { + next = next || inlineNormal; + return function(stream, state) { + stream.match(inlineRE(endChar)); + state.inline = state.f = next; + return type; + }; + } + + return { + startState: function() { + prevLineHasContent = false; + thisLineHasContent = false; + return { + f: blockNormal, + + block: blockNormal, + htmlState: CodeMirror.startState(htmlMode), + indentation: 0, + + inline: inlineNormal, + text: handleText, + + linkText: false, + linkTitle: false, + em: false, + strong: false, + header: false, + list: false, + quote: false + }; + }, + + copyState: function(s) { + return { + f: s.f, + + block: s.block, + htmlState: CodeMirror.copyState(htmlMode, s.htmlState), + indentation: s.indentation, + + localMode: s.localMode, + localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null, + + inline: s.inline, + text: s.text, + linkTitle: s.linkTitle, + em: s.em, + strong: s.strong, + header: s.header, + list: s.list, + quote: s.quote, + md_inside: s.md_inside + }; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (stream.match(/^\s*$/, true)) { + prevLineHasContent = false; + return blankLine(state); + } else { + if(thisLineHasContent){ + prevLineHasContent = true; + thisLineHasContent = false; + } + thisLineHasContent = true; + } + + // Reset state.header + state.header = false; + + // Reset state.code + state.code = false; + + state.f = state.block; + var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length; + var difference = Math.floor((indentation - state.indentation) / 4) * 4; + if (difference > 4) difference = 4; + indentation = state.indentation + difference; + state.indentationDiff = indentation - state.indentation; + state.indentation = indentation; + if (indentation > 0) { return null; } + } + return state.f(stream, state); + }, + + blankLine: blankLine, + + getType: getType + }; + +}, "xml"); + +CodeMirror.defineMIME("text/x-markdown", "markdown"); diff --git a/app/src/main/assets/mode/mysql/mysql.js b/app/src/main/assets/mode/mysql/mysql.js new file mode 100644 index 0000000000000000000000000000000000000000..3098d775760e696687358574cb17f1db67d4ec34 --- /dev/null +++ b/app/src/main/assets/mode/mysql/mysql.js @@ -0,0 +1,186 @@ +/* + * MySQL Mode for CodeMirror 2 by MySQL-Tools + * @author James Thorne (partydroid) + * @link http://github.com/partydroid/MySQL-Tools + * @link http://mysqltools.org + * @version 02/Jan/2012 +*/ +CodeMirror.defineMode("mysql", function(config) { + var indentUnit = config.indentUnit; + var curPunc; + + function wordRegexp(words) { + return new RegExp("^(?:" + words.join("|") + ")$", "i"); + } + var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", + "isblank", "isliteral", "union", "a"]); + var keywords = wordRegexp([ + ('ACCESSIBLE'),('ALTER'),('AS'),('BEFORE'),('BINARY'),('BY'),('CASE'),('CHARACTER'),('COLUMN'),('CONTINUE'),('CROSS'),('CURRENT_TIMESTAMP'),('DATABASE'),('DAY_MICROSECOND'),('DEC'),('DEFAULT'), + ('DESC'),('DISTINCT'),('DOUBLE'),('EACH'),('ENCLOSED'),('EXIT'),('FETCH'),('FLOAT8'),('FOREIGN'),('GRANT'),('HIGH_PRIORITY'),('HOUR_SECOND'),('IN'),('INNER'),('INSERT'),('INT2'),('INT8'), + ('INTO'),('JOIN'),('KILL'),('LEFT'),('LINEAR'),('LOCALTIME'),('LONG'),('LOOP'),('MATCH'),('MEDIUMTEXT'),('MINUTE_SECOND'),('NATURAL'),('NULL'),('OPTIMIZE'),('OR'),('OUTER'),('PRIMARY'), + ('RANGE'),('READ_WRITE'),('REGEXP'),('REPEAT'),('RESTRICT'),('RIGHT'),('SCHEMAS'),('SENSITIVE'),('SHOW'),('SPECIFIC'),('SQLSTATE'),('SQL_CALC_FOUND_ROWS'),('STARTING'),('TERMINATED'), + ('TINYINT'),('TRAILING'),('UNDO'),('UNLOCK'),('USAGE'),('UTC_DATE'),('VALUES'),('VARCHARACTER'),('WHERE'),('WRITE'),('ZEROFILL'),('ALL'),('AND'),('ASENSITIVE'),('BIGINT'),('BOTH'),('CASCADE'), + ('CHAR'),('COLLATE'),('CONSTRAINT'),('CREATE'),('CURRENT_TIME'),('CURSOR'),('DAY_HOUR'),('DAY_SECOND'),('DECLARE'),('DELETE'),('DETERMINISTIC'),('DIV'),('DUAL'),('ELSEIF'),('EXISTS'),('FALSE'), + ('FLOAT4'),('FORCE'),('FULLTEXT'),('HAVING'),('HOUR_MINUTE'),('IGNORE'),('INFILE'),('INSENSITIVE'),('INT1'),('INT4'),('INTERVAL'),('ITERATE'),('KEYS'),('LEAVE'),('LIMIT'),('LOAD'),('LOCK'), + ('LONGTEXT'),('MASTER_SSL_VERIFY_SERVER_CERT'),('MEDIUMINT'),('MINUTE_MICROSECOND'),('MODIFIES'),('NO_WRITE_TO_BINLOG'),('ON'),('OPTIONALLY'),('OUT'),('PRECISION'),('PURGE'),('READS'), + ('REFERENCES'),('RENAME'),('REQUIRE'),('REVOKE'),('SCHEMA'),('SELECT'),('SET'),('SPATIAL'),('SQLEXCEPTION'),('SQL_BIG_RESULT'),('SSL'),('TABLE'),('TINYBLOB'),('TO'),('TRUE'),('UNIQUE'), + ('UPDATE'),('USING'),('UTC_TIMESTAMP'),('VARCHAR'),('WHEN'),('WITH'),('YEAR_MONTH'),('ADD'),('ANALYZE'),('ASC'),('BETWEEN'),('BLOB'),('CALL'),('CHANGE'),('CHECK'),('CONDITION'),('CONVERT'), + ('CURRENT_DATE'),('CURRENT_USER'),('DATABASES'),('DAY_MINUTE'),('DECIMAL'),('DELAYED'),('DESCRIBE'),('DISTINCTROW'),('DROP'),('ELSE'),('ESCAPED'),('EXPLAIN'),('FLOAT'),('FOR'),('FROM'), + ('GROUP'),('HOUR_MICROSECOND'),('IF'),('INDEX'),('INOUT'),('INT'),('INT3'),('INTEGER'),('IS'),('KEY'),('LEADING'),('LIKE'),('LINES'),('LOCALTIMESTAMP'),('LONGBLOB'),('LOW_PRIORITY'), + ('MEDIUMBLOB'),('MIDDLEINT'),('MOD'),('NOT'),('NUMERIC'),('OPTION'),('ORDER'),('OUTFILE'),('PROCEDURE'),('READ'),('REAL'),('RELEASE'),('REPLACE'),('RETURN'),('RLIKE'),('SECOND_MICROSECOND'), + ('SEPARATOR'),('SMALLINT'),('SQL'),('SQLWARNING'),('SQL_SMALL_RESULT'),('STRAIGHT_JOIN'),('THEN'),('TINYTEXT'),('TRIGGER'),('UNION'),('UNSIGNED'),('USE'),('UTC_TIME'),('VARBINARY'),('VARYING'), + ('WHILE'),('XOR'),('FULL'),('COLUMNS'),('MIN'),('MAX'),('STDEV'),('COUNT') + ]); + var operatorChars = /[*+\-<>=&|]/; + + function tokenBase(stream, state) { + var ch = stream.next(); + curPunc = null; + if (ch == "$" || ch == "?") { + stream.match(/^[\w\d]*/); + return "variable-2"; + } + else if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) { + stream.match(/^[^\s\u00a0>]*>?/); + return "atom"; + } + else if (ch == "\"" || ch == "'") { + state.tokenize = tokenLiteral(ch); + return state.tokenize(stream, state); + } + else if (ch == "`") { + state.tokenize = tokenOpLiteral(ch); + return state.tokenize(stream, state); + } + else if (/[{}\(\),\.;\[\]]/.test(ch)) { + curPunc = ch; + return null; + } + else if (ch == "-") { + var ch2 = stream.next(); + if (ch2=="-") { + stream.skipToEnd(); + return "comment"; + } + } + else if (operatorChars.test(ch)) { + stream.eatWhile(operatorChars); + return null; + } + else if (ch == ":") { + stream.eatWhile(/[\w\d\._\-]/); + return "atom"; + } + else { + stream.eatWhile(/[_\w\d]/); + if (stream.eat(":")) { + stream.eatWhile(/[\w\d_\-]/); + return "atom"; + } + var word = stream.current(), type; + if (ops.test(word)) + return null; + else if (keywords.test(word)) + return "keyword"; + else + return "variable"; + } + } + + function tokenLiteral(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) { + state.tokenize = tokenBase; + break; + } + escaped = !escaped && ch == "\\"; + } + return "string"; + }; + } + + function tokenOpLiteral(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) { + state.tokenize = tokenBase; + break; + } + escaped = !escaped && ch == "\\"; + } + return "variable-2"; + }; + } + + + function pushContext(state, type, col) { + state.context = {prev: state.context, indent: state.indent, col: col, type: type}; + } + function popContext(state) { + state.indent = state.context.indent; + state.context = state.context.prev; + } + + return { + startState: function(base) { + return {tokenize: tokenBase, + context: null, + indent: 0, + col: 0}; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (state.context && state.context.align == null) state.context.align = false; + state.indent = stream.indentation(); + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + + if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") { + state.context.align = true; + } + + if (curPunc == "(") pushContext(state, ")", stream.column()); + else if (curPunc == "[") pushContext(state, "]", stream.column()); + else if (curPunc == "{") pushContext(state, "}", stream.column()); + else if (/[\]\}\)]/.test(curPunc)) { + while (state.context && state.context.type == "pattern") popContext(state); + if (state.context && curPunc == state.context.type) popContext(state); + } + else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state); + else if (/atom|string|variable/.test(style) && state.context) { + if (/[\}\]]/.test(state.context.type)) + pushContext(state, "pattern", stream.column()); + else if (state.context.type == "pattern" && !state.context.align) { + state.context.align = true; + state.context.col = stream.column(); + } + } + + return style; + }, + + indent: function(state, textAfter) { + var firstChar = textAfter && textAfter.charAt(0); + var context = state.context; + if (/[\]\}]/.test(firstChar)) + while (context && context.type == "pattern") context = context.prev; + + var closing = context && firstChar == context.type; + if (!context) + return 0; + else if (context.type == "pattern") + return context.col; + else if (context.align) + return context.col + (closing ? 0 : 1); + else + return context.indent + (closing ? 0 : indentUnit); + } + }; +}); + +CodeMirror.defineMIME("text/x-mysql", "mysql"); diff --git a/app/src/main/assets/mode/ntriples/ntriples.js b/app/src/main/assets/mode/ntriples/ntriples.js new file mode 100644 index 0000000000000000000000000000000000000000..abe6a1a0db6133eb2afdec8264ea9221d0b5d3dc --- /dev/null +++ b/app/src/main/assets/mode/ntriples/ntriples.js @@ -0,0 +1,172 @@ +/********************************************************** +* This script provides syntax highlighting support for +* the Ntriples format. +* Ntriples format specification: +* http://www.w3.org/TR/rdf-testcases/#ntriples +***********************************************************/ + +/* + The following expression defines the defined ASF grammar transitions. + + pre_subject -> + { + ( writing_subject_uri | writing_bnode_uri ) + -> pre_predicate + -> writing_predicate_uri + -> pre_object + -> writing_object_uri | writing_object_bnode | + ( + writing_object_literal + -> writing_literal_lang | writing_literal_type + ) + -> post_object + -> BEGIN + } otherwise { + -> ERROR + } +*/ +CodeMirror.defineMode("ntriples", function() { + + var Location = { + PRE_SUBJECT : 0, + WRITING_SUB_URI : 1, + WRITING_BNODE_URI : 2, + PRE_PRED : 3, + WRITING_PRED_URI : 4, + PRE_OBJ : 5, + WRITING_OBJ_URI : 6, + WRITING_OBJ_BNODE : 7, + WRITING_OBJ_LITERAL : 8, + WRITING_LIT_LANG : 9, + WRITING_LIT_TYPE : 10, + POST_OBJ : 11, + ERROR : 12 + }; + function transitState(currState, c) { + var currLocation = currState.location; + var ret; + + // Opening. + if (currLocation == Location.PRE_SUBJECT && c == '<') ret = Location.WRITING_SUB_URI; + else if(currLocation == Location.PRE_SUBJECT && c == '_') ret = Location.WRITING_BNODE_URI; + else if(currLocation == Location.PRE_PRED && c == '<') ret = Location.WRITING_PRED_URI; + else if(currLocation == Location.PRE_OBJ && c == '<') ret = Location.WRITING_OBJ_URI; + else if(currLocation == Location.PRE_OBJ && c == '_') ret = Location.WRITING_OBJ_BNODE; + else if(currLocation == Location.PRE_OBJ && c == '"') ret = Location.WRITING_OBJ_LITERAL; + + // Closing. + else if(currLocation == Location.WRITING_SUB_URI && c == '>') ret = Location.PRE_PRED; + else if(currLocation == Location.WRITING_BNODE_URI && c == ' ') ret = Location.PRE_PRED; + else if(currLocation == Location.WRITING_PRED_URI && c == '>') ret = Location.PRE_OBJ; + else if(currLocation == Location.WRITING_OBJ_URI && c == '>') ret = Location.POST_OBJ; + else if(currLocation == Location.WRITING_OBJ_BNODE && c == ' ') ret = Location.POST_OBJ; + else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '"') ret = Location.POST_OBJ; + else if(currLocation == Location.WRITING_LIT_LANG && c == ' ') ret = Location.POST_OBJ; + else if(currLocation == Location.WRITING_LIT_TYPE && c == '>') ret = Location.POST_OBJ; + + // Closing typed and language literal. + else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '@') ret = Location.WRITING_LIT_LANG; + else if(currLocation == Location.WRITING_OBJ_LITERAL && c == '^') ret = Location.WRITING_LIT_TYPE; + + // Spaces. + else if( c == ' ' && + ( + currLocation == Location.PRE_SUBJECT || + currLocation == Location.PRE_PRED || + currLocation == Location.PRE_OBJ || + currLocation == Location.POST_OBJ + ) + ) ret = currLocation; + + // Reset. + else if(currLocation == Location.POST_OBJ && c == '.') ret = Location.PRE_SUBJECT; + + // Error + else ret = Location.ERROR; + + currState.location=ret; + } + + var untilSpace = function(c) { return c != ' '; }; + var untilEndURI = function(c) { return c != '>'; }; + return { + startState: function() { + return { + location : Location.PRE_SUBJECT, + uris : [], + anchors : [], + bnodes : [], + langs : [], + types : [] + }; + }, + token: function(stream, state) { + var ch = stream.next(); + if(ch == '<') { + transitState(state, ch); + var parsedURI = ''; + stream.eatWhile( function(c) { if( c != '#' && c != '>' ) { parsedURI += c; return true; } return false;} ); + state.uris.push(parsedURI); + if( stream.match('#', false) ) return 'variable'; + stream.next(); + transitState(state, '>'); + return 'variable'; + } + if(ch == '#') { + var parsedAnchor = ''; + stream.eatWhile(function(c) { if(c != '>' && c != ' ') { parsedAnchor+= c; return true; } return false;}); + state.anchors.push(parsedAnchor); + return 'variable-2'; + } + if(ch == '>') { + transitState(state, '>'); + return 'variable'; + } + if(ch == '_') { + transitState(state, ch); + var parsedBNode = ''; + stream.eatWhile(function(c) { if( c != ' ' ) { parsedBNode += c; return true; } return false;}); + state.bnodes.push(parsedBNode); + stream.next(); + transitState(state, ' '); + return 'builtin'; + } + if(ch == '"') { + transitState(state, ch); + stream.eatWhile( function(c) { return c != '"'; } ); + stream.next(); + if( stream.peek() != '@' && stream.peek() != '^' ) { + transitState(state, '"'); + } + return 'string'; + } + if( ch == '@' ) { + transitState(state, '@'); + var parsedLang = ''; + stream.eatWhile(function(c) { if( c != ' ' ) { parsedLang += c; return true; } return false;}); + state.langs.push(parsedLang); + stream.next(); + transitState(state, ' '); + return 'string-2'; + } + if( ch == '^' ) { + stream.next(); + transitState(state, '^'); + var parsedType = ''; + stream.eatWhile(function(c) { if( c != '>' ) { parsedType += c; return true; } return false;} ); + state.types.push(parsedType); + stream.next(); + transitState(state, '>'); + return 'variable'; + } + if( ch == ' ' ) { + transitState(state, ch); + } + if( ch == '.' ) { + transitState(state, ch); + } + } + }; +}); + +CodeMirror.defineMIME("text/n-triples", "ntriples"); diff --git a/app/src/main/assets/mode/ocaml/ocaml.js b/app/src/main/assets/mode/ocaml/ocaml.js new file mode 100644 index 0000000000000000000000000000000000000000..81edfd88a977d3a5e9bbb1047b5905dd64e3b77c --- /dev/null +++ b/app/src/main/assets/mode/ocaml/ocaml.js @@ -0,0 +1,114 @@ +CodeMirror.defineMode('ocaml', function(config) { + + var words = { + 'true': 'atom', + 'false': 'atom', + 'let': 'keyword', + 'rec': 'keyword', + 'in': 'keyword', + 'of': 'keyword', + 'and': 'keyword', + 'succ': 'keyword', + 'if': 'keyword', + 'then': 'keyword', + 'else': 'keyword', + 'for': 'keyword', + 'to': 'keyword', + 'while': 'keyword', + 'do': 'keyword', + 'done': 'keyword', + 'fun': 'keyword', + 'function': 'keyword', + 'val': 'keyword', + 'type': 'keyword', + 'mutable': 'keyword', + 'match': 'keyword', + 'with': 'keyword', + 'try': 'keyword', + 'raise': 'keyword', + 'begin': 'keyword', + 'end': 'keyword', + 'open': 'builtin', + 'trace': 'builtin', + 'ignore': 'builtin', + 'exit': 'builtin', + 'print_string': 'builtin', + 'print_endline': 'builtin' + }; + + function tokenBase(stream, state) { + var sol = stream.sol(); + var ch = stream.next(); + + if (ch === '"') { + state.tokenize = tokenString; + return state.tokenize(stream, state); + } + if (ch === '(') { + if (stream.eat('*')) { + state.commentLevel++; + state.tokenize = tokenComment; + return state.tokenize(stream, state); + } + } + if (ch === '~') { + stream.eatWhile(/\w/); + return 'variable-2'; + } + if (ch === '`') { + stream.eatWhile(/\w/); + return 'quote'; + } + if (/\d/.test(ch)) { + stream.eatWhile(/[\d]/); + if (stream.eat('.')) { + stream.eatWhile(/[\d]/); + } + return 'number'; + } + if ( /[+\-*&%=<>!?|]/.test(ch)) { + return 'operator'; + } + stream.eatWhile(/\w/); + var cur = stream.current(); + return words[cur] || 'variable'; + } + + function tokenString(stream, state) { + var next, end = false, escaped = false; + while ((next = stream.next()) != null) { + if (next === '"' && !escaped) { + end = true; + break; + } + escaped = !escaped && next === '\\'; + } + if (end && !escaped) { + state.tokenize = tokenBase; + } + return 'string'; + }; + + function tokenComment(stream, state) { + var prev, next; + while(state.commentLevel > 0 && (next = stream.next()) != null) { + if (prev === '(' && next === '*') state.commentLevel++; + if (prev === '*' && next === ')') state.commentLevel--; + prev = next; + } + if (state.commentLevel <= 0) { + state.tokenize = tokenBase; + } + return 'comment'; + } + + return { + startState: function() {return {tokenize: tokenBase, commentLevel: 0};}, + token: function(stream, state) { + if (stream.eatSpace()) return null; + return state.tokenize(stream, state); + } + }; +}); + +CodeMirror.defineMIME('text/x-ocaml', 'ocaml'); diff --git a/app/src/main/assets/mode/pascal/LICENSE b/app/src/main/assets/mode/pascal/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..8e3747e74808d9c135b4afff23ce215d074d9973 --- /dev/null +++ b/app/src/main/assets/mode/pascal/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2011 souceLair + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/app/src/main/assets/mode/pascal/pascal.js b/app/src/main/assets/mode/pascal/pascal.js new file mode 100644 index 0000000000000000000000000000000000000000..b11d2d0a09311469d1062ac02d488a759e264f36 --- /dev/null +++ b/app/src/main/assets/mode/pascal/pascal.js @@ -0,0 +1,94 @@ +CodeMirror.defineMode("pascal", function(config) { + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + var keywords = words("and array begin case const div do downto else end file for forward integer " + + "boolean char function goto if in label mod nil not of or packed procedure " + + "program record repeat set string then to type until var while with"); + var atoms = {"null": true}; + + var isOperatorChar = /[+\-*&%=<>!?|\/]/; + + function tokenBase(stream, state) { + var ch = stream.next(); + if (ch == "#" && state.startOfLine) { + stream.skipToEnd(); + return "meta"; + } + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (ch == "(" && stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + return null; + } + if (/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + return "number"; + } + if (ch == "/") { + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "operator"; + } + stream.eatWhile(/[\w\$_]/); + var cur = stream.current(); + if (keywords.propertyIsEnumerable(cur)) return "keyword"; + if (atoms.propertyIsEnumerable(cur)) return "atom"; + return "variable"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !escaped) state.tokenize = null; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == ")" && maybeEnd) { + state.tokenize = null; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + // Interface + + return { + startState: function(basecolumn) { + return {tokenize: null}; + }, + + token: function(stream, state) { + if (stream.eatSpace()) return null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment" || style == "meta") return style; + return style; + }, + + electricChars: "{}" + }; +}); + +CodeMirror.defineMIME("text/x-pascal", "pascal"); diff --git a/app/src/main/assets/mode/perl/LICENSE b/app/src/main/assets/mode/perl/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..96f4115af8a96e1fb2ab78d5ac0d9490f16c7469 --- /dev/null +++ b/app/src/main/assets/mode/perl/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2011 by Sabaca under the MIT license. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/app/src/main/assets/mode/perl/perl.js b/app/src/main/assets/mode/perl/perl.js new file mode 100644 index 0000000000000000000000000000000000000000..a6446294f05256f07ec1c24f026f8c6f6c398e6f --- /dev/null +++ b/app/src/main/assets/mode/perl/perl.js @@ -0,0 +1,816 @@ +// CodeMirror2 mode/perl/perl.js (text/x-perl) beta 0.10 (2011-11-08) +// This is a part of CodeMirror from https://github.com/sabaca/CodeMirror_mode_perl (mail@sabaca.com) +CodeMirror.defineMode("perl",function(config,parserConfig){ + // http://perldoc.perl.org + var PERL={ // null - magic touch + // 1 - keyword + // 2 - def + // 3 - atom + // 4 - operator + // 5 - variable-2 (predefined) + // [x,y] - x=1,2,3; y=must be defined if x{...} + // PERL operators + '->' : 4, + '++' : 4, + '--' : 4, + '**' : 4, + // ! ~ \ and unary + and - + '=~' : 4, + '!~' : 4, + '*' : 4, + '/' : 4, + '%' : 4, + 'x' : 4, + '+' : 4, + '-' : 4, + '.' : 4, + '<<' : 4, + '>>' : 4, + // named unary operators + '<' : 4, + '>' : 4, + '<=' : 4, + '>=' : 4, + 'lt' : 4, + 'gt' : 4, + 'le' : 4, + 'ge' : 4, + '==' : 4, + '!=' : 4, + '<=>' : 4, + 'eq' : 4, + 'ne' : 4, + 'cmp' : 4, + '~~' : 4, + '&' : 4, + '|' : 4, + '^' : 4, + '&&' : 4, + '||' : 4, + '//' : 4, + '..' : 4, + '...' : 4, + '?' : 4, + ':' : 4, + '=' : 4, + '+=' : 4, + '-=' : 4, + '*=' : 4, // etc. ??? + ',' : 4, + '=>' : 4, + '::' : 4, + // list operators (rightward) + 'not' : 4, + 'and' : 4, + 'or' : 4, + 'xor' : 4, + // PERL predefined variables (I know, what this is a paranoid idea, but may be needed for people, who learn PERL, and for me as well, ...and may be for you?;) + 'BEGIN' : [5,1], + 'END' : [5,1], + 'PRINT' : [5,1], + 'PRINTF' : [5,1], + 'GETC' : [5,1], + 'READ' : [5,1], + 'READLINE' : [5,1], + 'DESTROY' : [5,1], + 'TIE' : [5,1], + 'TIEHANDLE' : [5,1], + 'UNTIE' : [5,1], + 'STDIN' : 5, + 'STDIN_TOP' : 5, + 'STDOUT' : 5, + 'STDOUT_TOP' : 5, + 'STDERR' : 5, + 'STDERR_TOP' : 5, + '$ARG' : 5, + '$_' : 5, + '@ARG' : 5, + '@_' : 5, + '$LIST_SEPARATOR' : 5, + '$"' : 5, + '$PROCESS_ID' : 5, + '$PID' : 5, + '$$' : 5, + '$REAL_GROUP_ID' : 5, + '$GID' : 5, + '$(' : 5, + '$EFFECTIVE_GROUP_ID' : 5, + '$EGID' : 5, + '$)' : 5, + '$PROGRAM_NAME' : 5, + '$0' : 5, + '$SUBSCRIPT_SEPARATOR' : 5, + '$SUBSEP' : 5, + '$;' : 5, + '$REAL_USER_ID' : 5, + '$UID' : 5, + '$<' : 5, + '$EFFECTIVE_USER_ID' : 5, + '$EUID' : 5, + '$>' : 5, + '$a' : 5, + '$b' : 5, + '$COMPILING' : 5, + '$^C' : 5, + '$DEBUGGING' : 5, + '$^D' : 5, + '${^ENCODING}' : 5, + '$ENV' : 5, + '%ENV' : 5, + '$SYSTEM_FD_MAX' : 5, + '$^F' : 5, + '@F' : 5, + '${^GLOBAL_PHASE}' : 5, + '$^H' : 5, + '%^H' : 5, + '@INC' : 5, + '%INC' : 5, + '$INPLACE_EDIT' : 5, + '$^I' : 5, + '$^M' : 5, + '$OSNAME' : 5, + '$^O' : 5, + '${^OPEN}' : 5, + '$PERLDB' : 5, + '$^P' : 5, + '$SIG' : 5, + '%SIG' : 5, + '$BASETIME' : 5, + '$^T' : 5, + '${^TAINT}' : 5, + '${^UNICODE}' : 5, + '${^UTF8CACHE}' : 5, + '${^UTF8LOCALE}' : 5, + '$PERL_VERSION' : 5, + '$^V' : 5, + '${^WIN32_SLOPPY_STAT}' : 5, + '$EXECUTABLE_NAME' : 5, + '$^X' : 5, + '$1' : 5, // - regexp $1, $2... + '$MATCH' : 5, + '$&' : 5, + '${^MATCH}' : 5, + '$PREMATCH' : 5, + '$`' : 5, + '${^PREMATCH}' : 5, + '$POSTMATCH' : 5, + "$'" : 5, + '${^POSTMATCH}' : 5, + '$LAST_PAREN_MATCH' : 5, + '$+' : 5, + '$LAST_SUBMATCH_RESULT' : 5, + '$^N' : 5, + '@LAST_MATCH_END' : 5, + '@+' : 5, + '%LAST_PAREN_MATCH' : 5, + '%+' : 5, + '@LAST_MATCH_START' : 5, + '@-' : 5, + '%LAST_MATCH_START' : 5, + '%-' : 5, + '$LAST_REGEXP_CODE_RESULT' : 5, + '$^R' : 5, + '${^RE_DEBUG_FLAGS}' : 5, + '${^RE_TRIE_MAXBUF}' : 5, + '$ARGV' : 5, + '@ARGV' : 5, + 'ARGV' : 5, + 'ARGVOUT' : 5, + '$OUTPUT_FIELD_SEPARATOR' : 5, + '$OFS' : 5, + '$,' : 5, + '$INPUT_LINE_NUMBER' : 5, + '$NR' : 5, + '$.' : 5, + '$INPUT_RECORD_SEPARATOR' : 5, + '$RS' : 5, + '$/' : 5, + '$OUTPUT_RECORD_SEPARATOR' : 5, + '$ORS' : 5, + '$\\' : 5, + '$OUTPUT_AUTOFLUSH' : 5, + '$|' : 5, + '$ACCUMULATOR' : 5, + '$^A' : 5, + '$FORMAT_FORMFEED' : 5, + '$^L' : 5, + '$FORMAT_PAGE_NUMBER' : 5, + '$%' : 5, + '$FORMAT_LINES_LEFT' : 5, + '$-' : 5, + '$FORMAT_LINE_BREAK_CHARACTERS' : 5, + '$:' : 5, + '$FORMAT_LINES_PER_PAGE' : 5, + '$=' : 5, + '$FORMAT_TOP_NAME' : 5, + '$^' : 5, + '$FORMAT_NAME' : 5, + '$~' : 5, + '${^CHILD_ERROR_NATIVE}' : 5, + '$EXTENDED_OS_ERROR' : 5, + '$^E' : 5, + '$EXCEPTIONS_BEING_CAUGHT' : 5, + '$^S' : 5, + '$WARNING' : 5, + '$^W' : 5, + '${^WARNING_BITS}' : 5, + '$OS_ERROR' : 5, + '$ERRNO' : 5, + '$!' : 5, + '%OS_ERROR' : 5, + '%ERRNO' : 5, + '%!' : 5, + '$CHILD_ERROR' : 5, + '$?' : 5, + '$EVAL_ERROR' : 5, + '$@' : 5, + '$OFMT' : 5, + '$#' : 5, + '$*' : 5, + '$ARRAY_BASE' : 5, + '$[' : 5, + '$OLD_PERL_VERSION' : 5, + '$]' : 5, + // PERL blocks + 'if' :[1,1], + elsif :[1,1], + 'else' :[1,1], + 'while' :[1,1], + unless :[1,1], + 'for' :[1,1], + foreach :[1,1], + // PERL functions + 'abs' :1, // - absolute value function + accept :1, // - accept an incoming socket connect + alarm :1, // - schedule a SIGALRM + 'atan2' :1, // - arctangent of Y/X in the range -PI to PI + bind :1, // - binds an address to a socket + binmode :1, // - prepare binary files for I/O + bless :1, // - create an object + bootstrap :1, // + 'break' :1, // - break out of a "given" block + caller :1, // - get context of the current subroutine call + chdir :1, // - change your current working directory + chmod :1, // - changes the permissions on a list of files + chomp :1, // - remove a trailing record separator from a string + chop :1, // - remove the last character from a string + chown :1, // - change the owership on a list of files + chr :1, // - get character this number represents + chroot :1, // - make directory new root for path lookups + close :1, // - close file (or pipe or socket) handle + closedir :1, // - close directory handle + connect :1, // - connect to a remote socket + 'continue' :[1,1], // - optional trailing block in a while or foreach + 'cos' :1, // - cosine function + crypt :1, // - one-way passwd-style encryption + dbmclose :1, // - breaks binding on a tied dbm file + dbmopen :1, // - create binding on a tied dbm file + 'default' :1, // + defined :1, // - test whether a value, variable, or function is defined + 'delete' :1, // - deletes a value from a hash + die :1, // - raise an exception or bail out + 'do' :1, // - turn a BLOCK into a TERM + dump :1, // - create an immediate core dump + each :1, // - retrieve the next key/value pair from a hash + endgrent :1, // - be done using group file + endhostent :1, // - be done using hosts file + endnetent :1, // - be done using networks file + endprotoent :1, // - be done using protocols file + endpwent :1, // - be done using passwd file + endservent :1, // - be done using services file + eof :1, // - test a filehandle for its end + 'eval' :1, // - catch exceptions or compile and run code + 'exec' :1, // - abandon this program to run another + exists :1, // - test whether a hash key is present + exit :1, // - terminate this program + 'exp' :1, // - raise I to a power + fcntl :1, // - file control system call + fileno :1, // - return file descriptor from filehandle + flock :1, // - lock an entire file with an advisory lock + fork :1, // - create a new process just like this one + format :1, // - declare a picture format with use by the write() function + formline :1, // - internal function used for formats + getc :1, // - get the next character from the filehandle + getgrent :1, // - get next group record + getgrgid :1, // - get group record given group user ID + getgrnam :1, // - get group record given group name + gethostbyaddr :1, // - get host record given its address + gethostbyname :1, // - get host record given name + gethostent :1, // - get next hosts record + getlogin :1, // - return who logged in at this tty + getnetbyaddr :1, // - get network record given its address + getnetbyname :1, // - get networks record given name + getnetent :1, // - get next networks record + getpeername :1, // - find the other end of a socket connection + getpgrp :1, // - get process group + getppid :1, // - get parent process ID + getpriority :1, // - get current nice value + getprotobyname :1, // - get protocol record given name + getprotobynumber :1, // - get protocol record numeric protocol + getprotoent :1, // - get next protocols record + getpwent :1, // - get next passwd record + getpwnam :1, // - get passwd record given user login name + getpwuid :1, // - get passwd record given user ID + getservbyname :1, // - get services record given its name + getservbyport :1, // - get services record given numeric port + getservent :1, // - get next services record + getsockname :1, // - retrieve the sockaddr for a given socket + getsockopt :1, // - get socket options on a given socket + given :1, // + glob :1, // - expand filenames using wildcards + gmtime :1, // - convert UNIX time into record or string using Greenwich time + 'goto' :1, // - create spaghetti code + grep :1, // - locate elements in a list test true against a given criterion + hex :1, // - convert a string to a hexadecimal number + 'import' :1, // - patch a module's namespace into your own + index :1, // - find a substring within a string + 'int' :1, // - get the integer portion of a number + ioctl :1, // - system-dependent device control system call + 'join' :1, // - join a list into a string using a separator + keys :1, // - retrieve list of indices from a hash + kill :1, // - send a signal to a process or process group + last :1, // - exit a block prematurely + lc :1, // - return lower-case version of a string + lcfirst :1, // - return a string with just the next letter in lower case + length :1, // - return the number of bytes in a string + 'link' :1, // - create a hard link in the filesytem + listen :1, // - register your socket as a server + local : 2, // - create a temporary value for a global variable (dynamic scoping) + localtime :1, // - convert UNIX time into record or string using local time + lock :1, // - get a thread lock on a variable, subroutine, or method + 'log' :1, // - retrieve the natural logarithm for a number + lstat :1, // - stat a symbolic link + m :null, // - match a string with a regular expression pattern + map :1, // - apply a change to a list to get back a new list with the changes + mkdir :1, // - create a directory + msgctl :1, // - SysV IPC message control operations + msgget :1, // - get SysV IPC message queue + msgrcv :1, // - receive a SysV IPC message from a message queue + msgsnd :1, // - send a SysV IPC message to a message queue + my : 2, // - declare and assign a local variable (lexical scoping) + 'new' :1, // + next :1, // - iterate a block prematurely + no :1, // - unimport some module symbols or semantics at compile time + oct :1, // - convert a string to an octal number + open :1, // - open a file, pipe, or descriptor + opendir :1, // - open a directory + ord :1, // - find a character's numeric representation + our : 2, // - declare and assign a package variable (lexical scoping) + pack :1, // - convert a list into a binary representation + 'package' :1, // - declare a separate global namespace + pipe :1, // - open a pair of connected filehandles + pop :1, // - remove the last element from an array and return it + pos :1, // - find or set the offset for the last/next m//g search + print :1, // - output a list to a filehandle + printf :1, // - output a formatted list to a filehandle + prototype :1, // - get the prototype (if any) of a subroutine + push :1, // - append one or more elements to an array + q :null, // - singly quote a string + qq :null, // - doubly quote a string + qr :null, // - Compile pattern + quotemeta :null, // - quote regular expression magic characters + qw :null, // - quote a list of words + qx :null, // - backquote quote a string + rand :1, // - retrieve the next pseudorandom number + read :1, // - fixed-length buffered input from a filehandle + readdir :1, // - get a directory from a directory handle + readline :1, // - fetch a record from a file + readlink :1, // - determine where a symbolic link is pointing + readpipe :1, // - execute a system command and collect standard output + recv :1, // - receive a message over a Socket + redo :1, // - start this loop iteration over again + ref :1, // - find out the type of thing being referenced + rename :1, // - change a filename + require :1, // - load in external functions from a library at runtime + reset :1, // - clear all variables of a given name + 'return' :1, // - get out of a function early + reverse :1, // - flip a string or a list + rewinddir :1, // - reset directory handle + rindex :1, // - right-to-left substring search + rmdir :1, // - remove a directory + s :null, // - replace a pattern with a string + say :1, // - print with newline + scalar :1, // - force a scalar context + seek :1, // - reposition file pointer for random-access I/O + seekdir :1, // - reposition directory pointer + select :1, // - reset default output or do I/O multiplexing + semctl :1, // - SysV semaphore control operations + semget :1, // - get set of SysV semaphores + semop :1, // - SysV semaphore operations + send :1, // - send a message over a socket + setgrent :1, // - prepare group file for use + sethostent :1, // - prepare hosts file for use + setnetent :1, // - prepare networks file for use + setpgrp :1, // - set the process group of a process + setpriority :1, // - set a process's nice value + setprotoent :1, // - prepare protocols file for use + setpwent :1, // - prepare passwd file for use + setservent :1, // - prepare services file for use + setsockopt :1, // - set some socket options + shift :1, // - remove the first element of an array, and return it + shmctl :1, // - SysV shared memory operations + shmget :1, // - get SysV shared memory segment identifier + shmread :1, // - read SysV shared memory + shmwrite :1, // - write SysV shared memory + shutdown :1, // - close down just half of a socket connection + 'sin' :1, // - return the sine of a number + sleep :1, // - block for some number of seconds + socket :1, // - create a socket + socketpair :1, // - create a pair of sockets + 'sort' :1, // - sort a list of values + splice :1, // - add or remove elements anywhere in an array + 'split' :1, // - split up a string using a regexp delimiter + sprintf :1, // - formatted print into a string + 'sqrt' :1, // - square root function + srand :1, // - seed the random number generator + stat :1, // - get a file's status information + state :1, // - declare and assign a state variable (persistent lexical scoping) + study :1, // - optimize input data for repeated searches + 'sub' :1, // - declare a subroutine, possibly anonymously + 'substr' :1, // - get or alter a portion of a stirng + symlink :1, // - create a symbolic link to a file + syscall :1, // - execute an arbitrary system call + sysopen :1, // - open a file, pipe, or descriptor + sysread :1, // - fixed-length unbuffered input from a filehandle + sysseek :1, // - position I/O pointer on handle used with sysread and syswrite + system :1, // - run a separate program + syswrite :1, // - fixed-length unbuffered output to a filehandle + tell :1, // - get current seekpointer on a filehandle + telldir :1, // - get current seekpointer on a directory handle + tie :1, // - bind a variable to an object class + tied :1, // - get a reference to the object underlying a tied variable + time :1, // - return number of seconds since 1970 + times :1, // - return elapsed time for self and child processes + tr :null, // - transliterate a string + truncate :1, // - shorten a file + uc :1, // - return upper-case version of a string + ucfirst :1, // - return a string with just the next letter in upper case + umask :1, // - set file creation mode mask + undef :1, // - remove a variable or function definition + unlink :1, // - remove one link to a file + unpack :1, // - convert binary structure into normal perl variables + unshift :1, // - prepend more elements to the beginning of a list + untie :1, // - break a tie binding to a variable + use :1, // - load in a module at compile time + utime :1, // - set a file's last access and modify times + values :1, // - return a list of the values in a hash + vec :1, // - test or set particular bits in a string + wait :1, // - wait for any child process to die + waitpid :1, // - wait for a particular child process to die + wantarray :1, // - get void vs scalar vs list context of current subroutine call + warn :1, // - print debugging info + when :1, // + write :1, // - print a picture record + y :null}; // - transliterate a string + + var RXstyle="string-2"; + var RXmodifiers=/[goseximacplud]/; // NOTE: "m", "s", "y" and "tr" need to correct real modifiers for each regexp type + + function tokenChain(stream,state,chain,style,tail){ // NOTE: chain.length > 2 is not working now (it's for s[...][...]geos;) + state.chain=null; // 12 3tail + state.style=null; + state.tail=null; + state.tokenize=function(stream,state){ + var e=false,c,i=0; + while(c=stream.next()){ + if(c===chain[i]&&!e){ + if(chain[++i]!==undefined){ + state.chain=chain[i]; + state.style=style; + state.tail=tail;} + else if(tail) + stream.eatWhile(tail); + state.tokenize=tokenPerl; + return style;} + e=!e&&c=="\\";} + return style;}; + return state.tokenize(stream,state);} + + function tokenSOMETHING(stream,state,string){ + state.tokenize=function(stream,state){ + if(stream.string==string) + state.tokenize=tokenPerl; + stream.skipToEnd(); + return "string";}; + return state.tokenize(stream,state);} + + function tokenPerl(stream,state){ + if(stream.eatSpace()) + return null; + if(state.chain) + return tokenChain(stream,state,state.chain,state.style,state.tail); + if(stream.match(/^\-?[\d\.]/,false)) + if(stream.match(/^(\-?(\d*\.\d+(e[+-]?\d+)?|\d+\.\d*)|0x[\da-fA-F]+|0b[01]+|\d+(e[+-]?\d+)?)/)) + return 'number'; + if(stream.match(/^<<(?=\w)/)){ // NOTE: <"],RXstyle,RXmodifiers);} + if(/[\^'"!~\/]/.test(c)){ + stream.eatSuffix(1); + return tokenChain(stream,state,[stream.eat(c)],RXstyle,RXmodifiers);}} + else if(c=="q"){ + c=stream.look(1); + if(c=="("){ + stream.eatSuffix(2); + return tokenChain(stream,state,[")"],"string");} + if(c=="["){ + stream.eatSuffix(2); + return tokenChain(stream,state,["]"],"string");} + if(c=="{"){ + stream.eatSuffix(2); + return tokenChain(stream,state,["}"],"string");} + if(c=="<"){ + stream.eatSuffix(2); + return tokenChain(stream,state,[">"],"string");} + if(/[\^'"!~\/]/.test(c)){ + stream.eatSuffix(1); + return tokenChain(stream,state,[stream.eat(c)],"string");}} + else if(c=="w"){ + c=stream.look(1); + if(c=="("){ + stream.eatSuffix(2); + return tokenChain(stream,state,[")"],"bracket");} + if(c=="["){ + stream.eatSuffix(2); + return tokenChain(stream,state,["]"],"bracket");} + if(c=="{"){ + stream.eatSuffix(2); + return tokenChain(stream,state,["}"],"bracket");} + if(c=="<"){ + stream.eatSuffix(2); + return tokenChain(stream,state,[">"],"bracket");} + if(/[\^'"!~\/]/.test(c)){ + stream.eatSuffix(1); + return tokenChain(stream,state,[stream.eat(c)],"bracket");}} + else if(c=="r"){ + c=stream.look(1); + if(c=="("){ + stream.eatSuffix(2); + return tokenChain(stream,state,[")"],RXstyle,RXmodifiers);} + if(c=="["){ + stream.eatSuffix(2); + return tokenChain(stream,state,["]"],RXstyle,RXmodifiers);} + if(c=="{"){ + stream.eatSuffix(2); + return tokenChain(stream,state,["}"],RXstyle,RXmodifiers);} + if(c=="<"){ + stream.eatSuffix(2); + return tokenChain(stream,state,[">"],RXstyle,RXmodifiers);} + if(/[\^'"!~\/]/.test(c)){ + stream.eatSuffix(1); + return tokenChain(stream,state,[stream.eat(c)],RXstyle,RXmodifiers);}} + else if(/[\^'"!~\/(\[{<]/.test(c)){ + if(c=="("){ + stream.eatSuffix(1); + return tokenChain(stream,state,[")"],"string");} + if(c=="["){ + stream.eatSuffix(1); + return tokenChain(stream,state,["]"],"string");} + if(c=="{"){ + stream.eatSuffix(1); + return tokenChain(stream,state,["}"],"string");} + if(c=="<"){ + stream.eatSuffix(1); + return tokenChain(stream,state,[">"],"string");} + if(/[\^'"!~\/]/.test(c)){ + return tokenChain(stream,state,[stream.eat(c)],"string");}}}} + if(ch=="m"){ + var c=stream.look(-2); + if(!(c&&/\w/.test(c))){ + c=stream.eat(/[(\[{<\^'"!~\/]/); + if(c){ + if(/[\^'"!~\/]/.test(c)){ + return tokenChain(stream,state,[c],RXstyle,RXmodifiers);} + if(c=="("){ + return tokenChain(stream,state,[")"],RXstyle,RXmodifiers);} + if(c=="["){ + return tokenChain(stream,state,["]"],RXstyle,RXmodifiers);} + if(c=="{"){ + return tokenChain(stream,state,["}"],RXstyle,RXmodifiers);} + if(c=="<"){ + return tokenChain(stream,state,[">"],RXstyle,RXmodifiers);}}}} + if(ch=="s"){ + var c=/[\/>\]})\w]/.test(stream.look(-2)); + if(!c){ + c=stream.eat(/[(\[{<\^'"!~\/]/); + if(c){ + if(c=="[") + return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers); + if(c=="{") + return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers); + if(c=="<") + return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers); + if(c=="(") + return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers); + return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}} + if(ch=="y"){ + var c=/[\/>\]})\w]/.test(stream.look(-2)); + if(!c){ + c=stream.eat(/[(\[{<\^'"!~\/]/); + if(c){ + if(c=="[") + return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers); + if(c=="{") + return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers); + if(c=="<") + return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers); + if(c=="(") + return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers); + return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}} + if(ch=="t"){ + var c=/[\/>\]})\w]/.test(stream.look(-2)); + if(!c){ + c=stream.eat("r");if(c){ + c=stream.eat(/[(\[{<\^'"!~\/]/); + if(c){ + if(c=="[") + return tokenChain(stream,state,["]","]"],RXstyle,RXmodifiers); + if(c=="{") + return tokenChain(stream,state,["}","}"],RXstyle,RXmodifiers); + if(c=="<") + return tokenChain(stream,state,[">",">"],RXstyle,RXmodifiers); + if(c=="(") + return tokenChain(stream,state,[")",")"],RXstyle,RXmodifiers); + return tokenChain(stream,state,[c,c],RXstyle,RXmodifiers);}}}} + if(ch=="`"){ + return tokenChain(stream,state,[ch],"variable-2");} + if(ch=="/"){ + if(!/~\s*$/.test(stream.prefix())) + return "operator"; + else + return tokenChain(stream,state,[ch],RXstyle,RXmodifiers);} + if(ch=="$"){ + var p=stream.pos; + if(stream.eatWhile(/\d/)||stream.eat("{")&&stream.eatWhile(/\d/)&&stream.eat("}")) + return "variable-2"; + else + stream.pos=p;} + if(/[$@%]/.test(ch)){ + var p=stream.pos; + if(stream.eat("^")&&stream.eat(/[A-Z]/)||!/[@$%&]/.test(stream.look(-2))&&stream.eat(/[=|\\\-#?@;:&`~\^!\[\]*'"$+.,\/<>()]/)){ + var c=stream.current(); + if(PERL[c]) + return "variable-2";} + stream.pos=p;} + if(/[$@%&]/.test(ch)){ + if(stream.eatWhile(/[\w$\[\]]/)||stream.eat("{")&&stream.eatWhile(/[\w$\[\]]/)&&stream.eat("}")){ + var c=stream.current(); + if(PERL[c]) + return "variable-2"; + else + return "variable";}} + if(ch=="#"){ + if(stream.look(-2)!="$"){ + stream.skipToEnd(); + return "comment";}} + if(/[:+\-\^*$&%@=<>!?|\/~\.]/.test(ch)){ + var p=stream.pos; + stream.eatWhile(/[:+\-\^*$&%@=<>!?|\/~\.]/); + if(PERL[stream.current()]) + return "operator"; + else + stream.pos=p;} + if(ch=="_"){ + if(stream.pos==1){ + if(stream.suffix(6)=="_END__"){ + return tokenChain(stream,state,['\0'],"comment");} + else if(stream.suffix(7)=="_DATA__"){ + return tokenChain(stream,state,['\0'],"variable-2");} + else if(stream.suffix(7)=="_C__"){ + return tokenChain(stream,state,['\0'],"string");}}} + if(/\w/.test(ch)){ + var p=stream.pos; + if(stream.look(-2)=="{"&&(stream.look(0)=="}"||stream.eatWhile(/\w/)&&stream.look(0)=="}")) + return "string"; + else + stream.pos=p;} + if(/[A-Z]/.test(ch)){ + var l=stream.look(-2); + var p=stream.pos; + stream.eatWhile(/[A-Z_]/); + if(/[\da-z]/.test(stream.look(0))){ + stream.pos=p;} + else{ + var c=PERL[stream.current()]; + if(!c) + return "meta"; + if(c[1]) + c=c[0]; + if(l!=":"){ + if(c==1) + return "keyword"; + else if(c==2) + return "def"; + else if(c==3) + return "atom"; + else if(c==4) + return "operator"; + else if(c==5) + return "variable-2"; + else + return "meta";} + else + return "meta";}} + if(/[a-zA-Z_]/.test(ch)){ + var l=stream.look(-2); + stream.eatWhile(/\w/); + var c=PERL[stream.current()]; + if(!c) + return "meta"; + if(c[1]) + c=c[0]; + if(l!=":"){ + if(c==1) + return "keyword"; + else if(c==2) + return "def"; + else if(c==3) + return "atom"; + else if(c==4) + return "operator"; + else if(c==5) + return "variable-2"; + else + return "meta";} + else + return "meta";} + return null;} + + return{ + startState:function(){ + return{ + tokenize:tokenPerl, + chain:null, + style:null, + tail:null};}, + token:function(stream,state){ + return (state.tokenize||tokenPerl)(stream,state);}, + electricChars:"{}"};}); + +CodeMirror.defineMIME("text/x-perl", "perl"); + +// it's like "peek", but need for look-ahead or look-behind if index < 0 +CodeMirror.StringStream.prototype.look=function(c){ + return this.string.charAt(this.pos+(c||0));}; + +// return a part of prefix of current stream from current position +CodeMirror.StringStream.prototype.prefix=function(c){ + if(c){ + var x=this.pos-c; + return this.string.substr((x>=0?x:0),c);} + else{ + return this.string.substr(0,this.pos-1);}}; + +// return a part of suffix of current stream from current position +CodeMirror.StringStream.prototype.suffix=function(c){ + var y=this.string.length; + var x=y-this.pos+1; + return this.string.substr(this.pos,(c&&c=(y=this.string.length-1)) + this.pos=y; + else + this.pos=x;}; diff --git a/app/src/main/assets/mode/php/php.js b/app/src/main/assets/mode/php/php.js new file mode 100644 index 0000000000000000000000000000000000000000..b94317c74907fc0225fe0b3b4c80077f919d0579 --- /dev/null +++ b/app/src/main/assets/mode/php/php.js @@ -0,0 +1,148 @@ +(function() { + function keywords(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + function heredoc(delim) { + return function(stream, state) { + if (stream.match(delim)) state.tokenize = null; + else stream.skipToEnd(); + return "string"; + }; + } + var phpConfig = { + name: "clike", + keywords: keywords("abstract and array as break case catch class clone const continue declare default " + + "do else elseif enddeclare endfor endforeach endif endswitch endwhile extends final " + + "for foreach function global goto if implements interface instanceof namespace " + + "new or private protected public static switch throw trait try use var while xor " + + "die echo empty exit eval include include_once isset list require require_once return " + + "print unset __halt_compiler self static parent"), + blockKeywords: keywords("catch do else elseif for foreach if switch try while"), + atoms: keywords("true false null TRUE FALSE NULL"), + multiLineStrings: true, + hooks: { + "$": function(stream, state) { + stream.eatWhile(/[\w\$_]/); + return "variable-2"; + }, + "<": function(stream, state) { + if (stream.match(/<", false)) stream.next(); + return "comment"; + }, + "/": function(stream, state) { + if (stream.eat("/")) { + while (!stream.eol() && !stream.match("?>", false)) stream.next(); + return "comment"; + } + return false; + } + } + }; + + CodeMirror.defineMode("php", function(config, parserConfig) { + var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true}); + var jsMode = CodeMirror.getMode(config, "javascript"); + var cssMode = CodeMirror.getMode(config, "css"); + var phpMode = CodeMirror.getMode(config, phpConfig); + + function dispatch(stream, state) { // TODO open PHP inside text/css + var isPHP = state.curMode == phpMode; + if (stream.sol() && state.pending != '"') state.pending = null; + if (state.curMode == htmlMode) { + if (stream.match(/^<\?\w*/)) { + state.curMode = phpMode; + state.curState = state.php; + state.curClose = "?>"; + return "meta"; + } + if (state.pending == '"') { + while (!stream.eol() && stream.next() != '"') {} + var style = "string"; + } else if (state.pending && stream.pos < state.pending.end) { + stream.pos = state.pending.end; + var style = state.pending.style; + } else { + var style = htmlMode.token(stream, state.curState); + } + state.pending = null; + var cur = stream.current(), openPHP = cur.search(/<\?/); + if (openPHP != -1) { + if (style == "string" && /\"$/.test(cur) && !/\?>/.test(cur)) state.pending = '"'; + else state.pending = {end: stream.pos, style: style}; + stream.backUp(cur.length - openPHP); + } else if (style == "tag" && stream.current() == ">" && state.curState.context) { + if (/^script$/i.test(state.curState.context.tagName)) { + state.curMode = jsMode; + state.curState = jsMode.startState(htmlMode.indent(state.curState, "")); + state.curClose = /^<\/\s*script\s*>/i; + } + else if (/^style$/i.test(state.curState.context.tagName)) { + state.curMode = cssMode; + state.curState = cssMode.startState(htmlMode.indent(state.curState, "")); + state.curClose = /^<\/\s*style\s*>/i; + } + } + return style; + } else if ((!isPHP || state.php.tokenize == null) && + stream.match(state.curClose, isPHP)) { + state.curMode = htmlMode; + state.curState = state.html; + state.curClose = null; + if (isPHP) return "meta"; + else return dispatch(stream, state); + } else { + return state.curMode.token(stream, state.curState); + } + } + + return { + startState: function() { + var html = htmlMode.startState(); + return {html: html, + php: phpMode.startState(), + curMode: parserConfig.startOpen ? phpMode : htmlMode, + curState: parserConfig.startOpen ? phpMode.startState() : html, + curClose: parserConfig.startOpen ? /^\?>/ : null, + mode: parserConfig.startOpen ? "php" : "html", + pending: null}; + }, + + copyState: function(state) { + var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html), + php = state.php, phpNew = CodeMirror.copyState(phpMode, php), cur; + if (state.curState == html) cur = htmlNew; + else if (state.curState == php) cur = phpNew; + else cur = CodeMirror.copyState(state.curMode, state.curState); + return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur, + curClose: state.curClose, mode: state.mode, + pending: state.pending}; + }, + + token: dispatch, + + indent: function(state, textAfter) { + if ((state.curMode != phpMode && /^\s*<\//.test(textAfter)) || + (state.curMode == phpMode && /^\?>/.test(textAfter))) + return htmlMode.indent(state.html, textAfter); + return state.curMode.indent(state.curState, textAfter); + }, + + electricChars: "/{}:", + + innerMode: function(state) { return {state: state.curState, mode: state.curMode}; } + }; + }, "xml", "clike", "javascript", "css"); + CodeMirror.defineMIME("application/x-httpd-php", "php"); + CodeMirror.defineMIME("application/x-httpd-php-open", {name: "php", startOpen: true}); + CodeMirror.defineMIME("text/x-php", phpConfig); +})(); diff --git a/app/src/main/assets/mode/pig/pig.js b/app/src/main/assets/mode/pig/pig.js new file mode 100644 index 0000000000000000000000000000000000000000..d55413f14db4e6f89c580cd4088c97b4117adef0 --- /dev/null +++ b/app/src/main/assets/mode/pig/pig.js @@ -0,0 +1,172 @@ +/* + * Pig Latin Mode for CodeMirror 2 + * @author Prasanth Jayachandran + * @link https://github.com/prasanthj/pig-codemirror-2 + * This implementation is adapted from PL/SQL mode in CodeMirror 2. +*/ +CodeMirror.defineMode("pig", function(config, parserConfig) { + var indentUnit = config.indentUnit, + keywords = parserConfig.keywords, + builtins = parserConfig.builtins, + types = parserConfig.types, + multiLineStrings = parserConfig.multiLineStrings; + + var isOperatorChar = /[*+\-%<>=&?:\/!|]/; + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + + var type; + function ret(tp, style) { + type = tp; + return style; + } + + function tokenComment(stream, state) { + var isEnd = false; + var ch; + while(ch = stream.next()) { + if(ch == "/" && isEnd) { + state.tokenize = tokenBase; + break; + } + isEnd = (ch == "*"); + } + return ret("comment", "comment"); + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while((next = stream.next()) != null) { + if (next == quote && !escaped) { + end = true; break; + } + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = tokenBase; + return ret("string", "error"); + }; + } + + function tokenBase(stream, state) { + var ch = stream.next(); + + // is a start of string? + if (ch == '"' || ch == "'") + return chain(stream, state, tokenString(ch)); + // is it one of the special chars + else if(/[\[\]{}\(\),;\.]/.test(ch)) + return ret(ch); + // is it a number? + else if(/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + return ret("number", "number"); + } + // multi line comment or operator + else if (ch == "/") { + if (stream.eat("*")) { + return chain(stream, state, tokenComment); + } + else { + stream.eatWhile(isOperatorChar); + return ret("operator", "operator"); + } + } + // single line comment or operator + else if (ch=="-") { + if(stream.eat("-")){ + stream.skipToEnd(); + return ret("comment", "comment"); + } + else { + stream.eatWhile(isOperatorChar); + return ret("operator", "operator"); + } + } + // is it an operator + else if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return ret("operator", "operator"); + } + else { + // get the while word + stream.eatWhile(/[\w\$_]/); + // is it one of the listed keywords? + if (keywords && keywords.propertyIsEnumerable(stream.current().toUpperCase())) { + if (stream.eat(")") || stream.eat(".")) { + //keywords can be used as variables like flatten(group), group.$0 etc.. + } + else { + return ("keyword", "keyword"); + } + } + // is it one of the builtin functions? + if (builtins && builtins.propertyIsEnumerable(stream.current().toUpperCase())) + { + return ("keyword", "variable-2"); + } + // is it one of the listed types? + if (types && types.propertyIsEnumerable(stream.current().toUpperCase())) + return ("keyword", "variable-3"); + // default is a 'variable' + return ret("variable", "pig-word"); + } + } + + // Interface + return { + startState: function(basecolumn) { + return { + tokenize: tokenBase, + startOfLine: true + }; + }, + + token: function(stream, state) { + if(stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + return style; + } + }; +}); + +(function() { + function keywords(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + // builtin funcs taken from trunk revision 1303237 + var pBuiltins = "ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL " + + "CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS " + + "DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG " + + "FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN " + + "INTSUM INVOKEFORDOUBLE INVOKEFORFLOAT INVOKEFORINT INVOKEFORLONG INVOKEFORSTRING INVOKER " + + "ISEMPTY JSONLOADER JSONMETADATA JSONSTORAGE LAST_INDEX_OF LCFIRST LOG LOG10 LOWER LONGABS " + + "LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA " + + "PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE " + + "SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG " + + "TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER "; + + // taken from QueryLexer.g + var pKeywords = "VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP " + + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL " + + "PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE " + + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE " + + "NEQ MATCHES TRUE FALSE "; + + // data types + var pTypes = "BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP "; + + CodeMirror.defineMIME("text/x-pig", { + name: "pig", + builtins: keywords(pBuiltins), + keywords: keywords(pKeywords), + types: keywords(pTypes) + }); +}()); diff --git a/app/src/main/assets/mode/plsql/plsql.js b/app/src/main/assets/mode/plsql/plsql.js new file mode 100644 index 0000000000000000000000000000000000000000..013deaf92bae33951a6691ad5303a979a656494e --- /dev/null +++ b/app/src/main/assets/mode/plsql/plsql.js @@ -0,0 +1,217 @@ +CodeMirror.defineMode("plsql", function(config, parserConfig) { + var indentUnit = config.indentUnit, + keywords = parserConfig.keywords, + functions = parserConfig.functions, + types = parserConfig.types, + sqlplus = parserConfig.sqlplus, + multiLineStrings = parserConfig.multiLineStrings; + var isOperatorChar = /[+\-*&%=<>!?:\/|]/; + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + + var type; + function ret(tp, style) { + type = tp; + return style; + } + + function tokenBase(stream, state) { + var ch = stream.next(); + // start of string? + if (ch == '"' || ch == "'") + return chain(stream, state, tokenString(ch)); + // is it one of the special signs []{}().,;? Seperator? + else if (/[\[\]{}\(\),;\.]/.test(ch)) + return ret(ch); + // start of a number value? + else if (/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + return ret("number", "number"); + } + // multi line comment or simple operator? + else if (ch == "/") { + if (stream.eat("*")) { + return chain(stream, state, tokenComment); + } + else { + stream.eatWhile(isOperatorChar); + return ret("operator", "operator"); + } + } + // single line comment or simple operator? + else if (ch == "-") { + if (stream.eat("-")) { + stream.skipToEnd(); + return ret("comment", "comment"); + } + else { + stream.eatWhile(isOperatorChar); + return ret("operator", "operator"); + } + } + // pl/sql variable? + else if (ch == "@" || ch == "$") { + stream.eatWhile(/[\w\d\$_]/); + return ret("word", "variable"); + } + // is it a operator? + else if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return ret("operator", "operator"); + } + else { + // get the whole word + stream.eatWhile(/[\w\$_]/); + // is it one of the listed keywords? + if (keywords && keywords.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "keyword"); + // is it one of the listed functions? + if (functions && functions.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "builtin"); + // is it one of the listed types? + if (types && types.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "variable-2"); + // is it one of the listed sqlplus keywords? + if (sqlplus && sqlplus.propertyIsEnumerable(stream.current().toLowerCase())) return ret("keyword", "variable-3"); + // default: just a "variable" + return ret("word", "variable"); + } + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = tokenBase; + return ret("string", "plsql-string"); + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return ret("comment", "plsql-comment"); + } + + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: tokenBase, + startOfLine: true + }; + }, + + token: function(stream, state) { + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + return style; + } + }; +}); + +(function() { + function keywords(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + var cKeywords = "abort accept access add all alter and any array arraylen as asc assert assign at attributes audit " + + "authorization avg " + + "base_table begin between binary_integer body boolean by " + + "case cast char char_base check close cluster clusters colauth column comment commit compress connect " + + "connected constant constraint crash create current currval cursor " + + "data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete " + + "desc digits dispose distinct do drop " + + "else elsif enable end entry escape exception exception_init exchange exclusive exists exit external " + + "fast fetch file for force form from function " + + "generic goto grant group " + + "having " + + "identified if immediate in increment index indexes indicator initial initrans insert interface intersect " + + "into is " + + "key " + + "level library like limited local lock log logging long loop " + + "master maxextents maxtrans member minextents minus mislabel mode modify multiset " + + "new next no noaudit nocompress nologging noparallel not nowait number_base " + + "object of off offline on online only open option or order out " + + "package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior " + + "private privileges procedure public " + + "raise range raw read rebuild record ref references refresh release rename replace resource restrict return " + + "returning reverse revoke rollback row rowid rowlabel rownum rows run " + + "savepoint schema segment select separate session set share snapshot some space split sql start statement " + + "storage subtype successful synonym " + + "tabauth table tables tablespace task terminate then to trigger truncate type " + + "union unique unlimited unrecoverable unusable update use using " + + "validate value values variable view views " + + "when whenever where while with work"; + + var cFunctions = "abs acos add_months ascii asin atan atan2 average " + + "bfilename " + + "ceil chartorowid chr concat convert cos cosh count " + + "decode deref dual dump dup_val_on_index " + + "empty error exp " + + "false floor found " + + "glb greatest " + + "hextoraw " + + "initcap instr instrb isopen " + + "last_day least lenght lenghtb ln lower lpad ltrim lub " + + "make_ref max min mod months_between " + + "new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower " + + "nls_sort nls_upper nlssort no_data_found notfound null nvl " + + "others " + + "power " + + "rawtohex reftohex round rowcount rowidtochar rpad rtrim " + + "sign sin sinh soundex sqlcode sqlerrm sqrt stddev substr substrb sum sysdate " + + "tan tanh to_char to_date to_label to_multi_byte to_number to_single_byte translate true trunc " + + "uid upper user userenv " + + "variance vsize"; + + var cTypes = "bfile blob " + + "character clob " + + "dec " + + "float " + + "int integer " + + "mlslabel " + + "natural naturaln nchar nclob number numeric nvarchar2 " + + "real rowtype " + + "signtype smallint string " + + "varchar varchar2"; + + var cSqlplus = "appinfo arraysize autocommit autoprint autorecovery autotrace " + + "blockterminator break btitle " + + "cmdsep colsep compatibility compute concat copycommit copytypecheck " + + "define describe " + + "echo editfile embedded escape exec execute " + + "feedback flagger flush " + + "heading headsep " + + "instance " + + "linesize lno loboffset logsource long longchunksize " + + "markup " + + "native newpage numformat numwidth " + + "pagesize pause pno " + + "recsep recsepchar release repfooter repheader " + + "serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber " + + "sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix " + + "tab term termout time timing trimout trimspool ttitle " + + "underline " + + "verify version " + + "wrap"; + + CodeMirror.defineMIME("text/x-plsql", { + name: "plsql", + keywords: keywords(cKeywords), + functions: keywords(cFunctions), + types: keywords(cTypes), + sqlplus: keywords(cSqlplus) + }); +}()); diff --git a/app/src/main/assets/mode/properties/properties.js b/app/src/main/assets/mode/properties/properties.js new file mode 100644 index 0000000000000000000000000000000000000000..d3a13c765dca5afbd2b8a69ebef4017033bdd68b --- /dev/null +++ b/app/src/main/assets/mode/properties/properties.js @@ -0,0 +1,63 @@ +CodeMirror.defineMode("properties", function() { + return { + token: function(stream, state) { + var sol = stream.sol() || state.afterSection; + var eol = stream.eol(); + + state.afterSection = false; + + if (sol) { + if (state.nextMultiline) { + state.inMultiline = true; + state.nextMultiline = false; + } else { + state.position = "def"; + } + } + + if (eol && ! state.nextMultiline) { + state.inMultiline = false; + state.position = "def"; + } + + if (sol) { + while(stream.eatSpace()); + } + + var ch = stream.next(); + + if (sol && (ch === "#" || ch === "!" || ch === ";")) { + state.position = "comment"; + stream.skipToEnd(); + return "comment"; + } else if (sol && ch === "[") { + state.afterSection = true; + stream.skipTo("]"); stream.eat("]"); + return "header"; + } else if (ch === "=" || ch === ":") { + state.position = "quote"; + return null; + } else if (ch === "\\" && state.position === "quote") { + if (stream.next() !== "u") { // u = Unicode sequence \u1234 + // Multiline value + state.nextMultiline = true; + } + } + + return state.position; + }, + + startState: function() { + return { + position : "def", // Current position, "def", "quote" or "comment" + nextMultiline : false, // Is the next line multiline value + inMultiline : false, // Is the current line a multiline value + afterSection : false // Did we just open a section + }; + } + + }; +}); + +CodeMirror.defineMIME("text/x-properties", "properties"); +CodeMirror.defineMIME("text/x-ini", "properties"); diff --git a/app/src/main/assets/mode/python/LICENSE.txt b/app/src/main/assets/mode/python/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..918866b42a7709e69143605918c4adc30863bbdd --- /dev/null +++ b/app/src/main/assets/mode/python/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2010 Timothy Farrell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/app/src/main/assets/mode/python/python.js b/app/src/main/assets/mode/python/python.js new file mode 100644 index 0000000000000000000000000000000000000000..fc5b9551c29bb2b4bfdf424cff66e4aab832106e --- /dev/null +++ b/app/src/main/assets/mode/python/python.js @@ -0,0 +1,338 @@ +CodeMirror.defineMode("python", function(conf, parserConf) { + var ERRORCLASS = 'error'; + + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b"); + } + + var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!]"); + var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]'); + var doubleOperators = new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))"); + var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); + var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))"); + var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*"); + + var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']); + var commonkeywords = ['as', 'assert', 'break', 'class', 'continue', + 'def', 'del', 'elif', 'else', 'except', 'finally', + 'for', 'from', 'global', 'if', 'import', + 'lambda', 'pass', 'raise', 'return', + 'try', 'while', 'with', 'yield']; + var commonBuiltins = ['abs', 'all', 'any', 'bin', 'bool', 'bytearray', 'callable', 'chr', + 'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod', + 'enumerate', 'eval', 'filter', 'float', 'format', 'frozenset', + 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', + 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', + 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', + 'object', 'oct', 'open', 'ord', 'pow', 'property', 'range', + 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', + 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', + 'type', 'vars', 'zip', '__import__', 'NotImplemented', + 'Ellipsis', '__debug__']; + var py2 = {'builtins': ['apply', 'basestring', 'buffer', 'cmp', 'coerce', 'execfile', + 'file', 'intern', 'long', 'raw_input', 'reduce', 'reload', + 'unichr', 'unicode', 'xrange', 'False', 'True', 'None'], + 'keywords': ['exec', 'print']}; + var py3 = {'builtins': ['ascii', 'bytes', 'exec', 'print'], + 'keywords': ['nonlocal', 'False', 'True', 'None']}; + + if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) { + commonkeywords = commonkeywords.concat(py3.keywords); + commonBuiltins = commonBuiltins.concat(py3.builtins); + var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i"); + } else { + commonkeywords = commonkeywords.concat(py2.keywords); + commonBuiltins = commonBuiltins.concat(py2.builtins); + var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i"); + } + var keywords = wordRegexp(commonkeywords); + var builtins = wordRegexp(commonBuiltins); + + var indentInfo = null; + + // tokenizers + function tokenBase(stream, state) { + // Handle scope changes + if (stream.sol()) { + var scopeOffset = state.scopes[0].offset; + if (stream.eatSpace()) { + var lineOffset = stream.indentation(); + if (lineOffset > scopeOffset) { + indentInfo = 'indent'; + } else if (lineOffset < scopeOffset) { + indentInfo = 'dedent'; + } + return null; + } else { + if (scopeOffset > 0) { + dedent(stream, state); + } + } + } + if (stream.eatSpace()) { + return null; + } + + var ch = stream.peek(); + + // Handle Comments + if (ch === '#') { + stream.skipToEnd(); + return 'comment'; + } + + // Handle Number Literals + if (stream.match(/^[0-9\.]/, false)) { + var floatLiteral = false; + // Floats + if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; } + if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; } + if (stream.match(/^\.\d+/)) { floatLiteral = true; } + if (floatLiteral) { + // Float literals may be "imaginary" + stream.eat(/J/i); + return 'number'; + } + // Integers + var intLiteral = false; + // Hex + if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; } + // Binary + if (stream.match(/^0b[01]+/i)) { intLiteral = true; } + // Octal + if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; } + // Decimal + if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) { + // Decimal literals may be "imaginary" + stream.eat(/J/i); + // TODO - Can you have imaginary longs? + intLiteral = true; + } + // Zero by itself with no other piece of number. + if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; } + if (intLiteral) { + // Integer literals may be "long" + stream.eat(/L/i); + return 'number'; + } + } + + // Handle Strings + if (stream.match(stringPrefixes)) { + state.tokenize = tokenStringFactory(stream.current()); + return state.tokenize(stream, state); + } + + // Handle operators and Delimiters + if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { + return null; + } + if (stream.match(doubleOperators) + || stream.match(singleOperators) + || stream.match(wordOperators)) { + return 'operator'; + } + if (stream.match(singleDelimiters)) { + return null; + } + + if (stream.match(keywords)) { + return 'keyword'; + } + + if (stream.match(builtins)) { + return 'builtin'; + } + + if (stream.match(identifiers)) { + return 'variable'; + } + + // Handle non-detected items + stream.next(); + return ERRORCLASS; + } + + function tokenStringFactory(delimiter) { + while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) { + delimiter = delimiter.substr(1); + } + var singleline = delimiter.length == 1; + var OUTCLASS = 'string'; + + return function tokenString(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^'"\\]/); + if (stream.eat('\\')) { + stream.next(); + if (singleline && stream.eol()) { + return OUTCLASS; + } + } else if (stream.match(delimiter)) { + state.tokenize = tokenBase; + return OUTCLASS; + } else { + stream.eat(/['"]/); + } + } + if (singleline) { + if (parserConf.singleLineStringErrors) { + return ERRORCLASS; + } else { + state.tokenize = tokenBase; + } + } + return OUTCLASS; + }; + } + + function indent(stream, state, type) { + type = type || 'py'; + var indentUnit = 0; + if (type === 'py') { + if (state.scopes[0].type !== 'py') { + state.scopes[0].offset = stream.indentation(); + return; + } + for (var i = 0; i < state.scopes.length; ++i) { + if (state.scopes[i].type === 'py') { + indentUnit = state.scopes[i].offset + conf.indentUnit; + break; + } + } + } else { + indentUnit = stream.column() + stream.current().length; + } + state.scopes.unshift({ + offset: indentUnit, + type: type + }); + } + + function dedent(stream, state, type) { + type = type || 'py'; + if (state.scopes.length == 1) return; + if (state.scopes[0].type === 'py') { + var _indent = stream.indentation(); + var _indent_index = -1; + for (var i = 0; i < state.scopes.length; ++i) { + if (_indent === state.scopes[i].offset) { + _indent_index = i; + break; + } + } + if (_indent_index === -1) { + return true; + } + while (state.scopes[0].offset !== _indent) { + state.scopes.shift(); + } + return false; + } else { + if (type === 'py') { + state.scopes[0].offset = stream.indentation(); + return false; + } else { + if (state.scopes[0].type != type) { + return true; + } + state.scopes.shift(); + return false; + } + } + } + + function tokenLexer(stream, state) { + indentInfo = null; + var style = state.tokenize(stream, state); + var current = stream.current(); + + // Handle '.' connected identifiers + if (current === '.') { + style = stream.match(identifiers, false) ? null : ERRORCLASS; + if (style === null && state.lastToken === 'meta') { + // Apply 'meta' style to '.' connected identifiers when + // appropriate. + style = 'meta'; + } + return style; + } + + // Handle decorators + if (current === '@') { + return stream.match(identifiers, false) ? 'meta' : ERRORCLASS; + } + + if ((style === 'variable' || style === 'builtin') + && state.lastToken === 'meta') { + style = 'meta'; + } + + // Handle scope changes. + if (current === 'pass' || current === 'return') { + state.dedent += 1; + } + if (current === 'lambda') state.lambda = true; + if ((current === ':' && !state.lambda && state.scopes[0].type == 'py') + || indentInfo === 'indent') { + indent(stream, state); + } + var delimiter_index = '[({'.indexOf(current); + if (delimiter_index !== -1) { + indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1)); + } + if (indentInfo === 'dedent') { + if (dedent(stream, state)) { + return ERRORCLASS; + } + } + delimiter_index = '])}'.indexOf(current); + if (delimiter_index !== -1) { + if (dedent(stream, state, current)) { + return ERRORCLASS; + } + } + if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'py') { + if (state.scopes.length > 1) state.scopes.shift(); + state.dedent -= 1; + } + + return style; + } + + var external = { + startState: function(basecolumn) { + return { + tokenize: tokenBase, + scopes: [{offset:basecolumn || 0, type:'py'}], + lastToken: null, + lambda: false, + dedent: 0 + }; + }, + + token: function(stream, state) { + var style = tokenLexer(stream, state); + + state.lastToken = style; + + if (stream.eol() && stream.lambda) { + state.lambda = false; + } + + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase) { + return 0; + } + + return state.scopes[0].offset; + } + + }; + return external; +}); + +CodeMirror.defineMIME("text/x-python", "python"); diff --git a/app/src/main/assets/mode/r/LICENSE b/app/src/main/assets/mode/r/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2510ae16cf7288da165dfe6a29717eb24a3a58cc --- /dev/null +++ b/app/src/main/assets/mode/r/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2011, Ubalo, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Ubalo, Inc nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL UBALO, INC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/app/src/main/assets/mode/r/r.js b/app/src/main/assets/mode/r/r.js new file mode 100644 index 0000000000000000000000000000000000000000..53647f23fa0c89e67f56e26307e5ce927f6aa26b --- /dev/null +++ b/app/src/main/assets/mode/r/r.js @@ -0,0 +1,141 @@ +CodeMirror.defineMode("r", function(config) { + function wordObj(str) { + var words = str.split(" "), res = {}; + for (var i = 0; i < words.length; ++i) res[words[i]] = true; + return res; + } + var atoms = wordObj("NULL NA Inf NaN NA_integer_ NA_real_ NA_complex_ NA_character_"); + var builtins = wordObj("list quote bquote eval return call parse deparse"); + var keywords = wordObj("if else repeat while function for in next break"); + var blockkeywords = wordObj("if else repeat while function for"); + var opChars = /[+\-*\/^<>=!&|~$:]/; + var curPunc; + + function tokenBase(stream, state) { + curPunc = null; + var ch = stream.next(); + if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } else if (ch == "0" && stream.eat("x")) { + stream.eatWhile(/[\da-f]/i); + return "number"; + } else if (ch == "." && stream.eat(/\d/)) { + stream.match(/\d*(?:e[+\-]?\d+)?/); + return "number"; + } else if (/\d/.test(ch)) { + stream.match(/\d*(?:\.\d+)?(?:e[+\-]\d+)?L?/); + return "number"; + } else if (ch == "'" || ch == '"') { + state.tokenize = tokenString(ch); + return "string"; + } else if (ch == "." && stream.match(/.[.\d]+/)) { + return "keyword"; + } else if (/[\w\.]/.test(ch) && ch != "_") { + stream.eatWhile(/[\w\.]/); + var word = stream.current(); + if (atoms.propertyIsEnumerable(word)) return "atom"; + if (keywords.propertyIsEnumerable(word)) { + if (blockkeywords.propertyIsEnumerable(word)) curPunc = "block"; + return "keyword"; + } + if (builtins.propertyIsEnumerable(word)) return "builtin"; + return "variable"; + } else if (ch == "%") { + if (stream.skipTo("%")) stream.next(); + return "variable-2"; + } else if (ch == "<" && stream.eat("-")) { + return "arrow"; + } else if (ch == "=" && state.ctx.argList) { + return "arg-is"; + } else if (opChars.test(ch)) { + if (ch == "$") return "dollar"; + stream.eatWhile(opChars); + return "operator"; + } else if (/[\(\){}\[\];]/.test(ch)) { + curPunc = ch; + if (ch == ";") return "semi"; + return null; + } else { + return null; + } + } + + function tokenString(quote) { + return function(stream, state) { + if (stream.eat("\\")) { + var ch = stream.next(); + if (ch == "x") stream.match(/^[a-f0-9]{2}/i); + else if ((ch == "u" || ch == "U") && stream.eat("{") && stream.skipTo("}")) stream.next(); + else if (ch == "u") stream.match(/^[a-f0-9]{4}/i); + else if (ch == "U") stream.match(/^[a-f0-9]{8}/i); + else if (/[0-7]/.test(ch)) stream.match(/^[0-7]{1,2}/); + return "string-2"; + } else { + var next; + while ((next = stream.next()) != null) { + if (next == quote) { state.tokenize = tokenBase; break; } + if (next == "\\") { stream.backUp(1); break; } + } + return "string"; + } + }; + } + + function push(state, type, stream) { + state.ctx = {type: type, + indent: state.indent, + align: null, + column: stream.column(), + prev: state.ctx}; + } + function pop(state) { + state.indent = state.ctx.indent; + state.ctx = state.ctx.prev; + } + + return { + startState: function(base) { + return {tokenize: tokenBase, + ctx: {type: "top", + indent: -config.indentUnit, + align: false}, + indent: 0, + afterIdent: false}; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (state.ctx.align == null) state.ctx.align = false; + state.indent = stream.indentation(); + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + if (style != "comment" && state.ctx.align == null) state.ctx.align = true; + + var ctype = state.ctx.type; + if ((curPunc == ";" || curPunc == "{" || curPunc == "}") && ctype == "block") pop(state); + if (curPunc == "{") push(state, "}", stream); + else if (curPunc == "(") { + push(state, ")", stream); + if (state.afterIdent) state.ctx.argList = true; + } + else if (curPunc == "[") push(state, "]", stream); + else if (curPunc == "block") push(state, "block", stream); + else if (curPunc == ctype) pop(state); + state.afterIdent = style == "variable" || style == "keyword"; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0), ctx = state.ctx, + closing = firstChar == ctx.type; + if (ctx.type == "block") return ctx.indent + (firstChar == "{" ? 0 : config.indentUnit); + else if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indent + (closing ? 0 : config.indentUnit); + } + }; +}); + +CodeMirror.defineMIME("text/x-rsrc", "r"); diff --git a/app/src/main/assets/mode/rpm/changes/changes.js b/app/src/main/assets/mode/rpm/changes/changes.js new file mode 100644 index 0000000000000000000000000000000000000000..cb45f9e524b9c5f174187d13c0880710282154dc --- /dev/null +++ b/app/src/main/assets/mode/rpm/changes/changes.js @@ -0,0 +1,19 @@ +CodeMirror.defineMode("changes", function(config, modeConfig) { + var headerSeperator = /^-+$/; + var headerLine = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ?\d{1,2} \d{2}:\d{2}(:\d{2})? [A-Z]{3,4} \d{4} - /; + var simpleEmail = /^[\w+.-]+@[\w.-]+/; + + return { + token: function(stream) { + if (stream.sol()) { + if (stream.match(headerSeperator)) { return 'tag'; } + if (stream.match(headerLine)) { return 'tag'; } + } + if (stream.match(simpleEmail)) { return 'string'; } + stream.next(); + return null; + } + }; +}); + +CodeMirror.defineMIME("text/x-rpm-changes", "changes"); diff --git a/app/src/main/assets/mode/rpm/spec/spec.css b/app/src/main/assets/mode/rpm/spec/spec.css new file mode 100644 index 0000000000000000000000000000000000000000..d0a5d430cacf07486842e77e55fdc429d70c6f70 --- /dev/null +++ b/app/src/main/assets/mode/rpm/spec/spec.css @@ -0,0 +1,5 @@ +.cm-s-default span.cm-preamble {color: #b26818; font-weight: bold;} +.cm-s-default span.cm-macro {color: #b218b2;} +.cm-s-default span.cm-section {color: green; font-weight: bold;} +.cm-s-default span.cm-script {color: red;} +.cm-s-default span.cm-issue {color: yellow;} diff --git a/app/src/main/assets/mode/rpm/spec/spec.js b/app/src/main/assets/mode/rpm/spec/spec.js new file mode 100644 index 0000000000000000000000000000000000000000..902db6ae1f243b28ca1bce8666b6d98b9dafa8ca --- /dev/null +++ b/app/src/main/assets/mode/rpm/spec/spec.js @@ -0,0 +1,66 @@ +// Quick and dirty spec file highlighting + +CodeMirror.defineMode("spec", function(config, modeConfig) { + var arch = /^(i386|i586|i686|x86_64|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/; + + var preamble = /^(Name|Version|Release|License|Summary|Url|Group|Source|BuildArch|BuildRequires|BuildRoot|AutoReqProv|Provides|Requires(\(\w+\))?|Obsoletes|Conflicts|Recommends|Source\d*|Patch\d*|ExclusiveArch|NoSource|Supplements):/; + var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preun|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/; + var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros + var control_flow_simple = /^%(else|endif)/; // rpm control flow macros + var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros + + return { + startState: function () { + return { + controlFlow: false, + macroParameters: false, + section: false + }; + }, + token: function (stream, state) { + var ch = stream.peek(); + if (ch == "#") { stream.skipToEnd(); return "comment"; } + + if (stream.sol()) { + if (stream.match(preamble)) { return "preamble"; } + if (stream.match(section)) { return "section"; } + } + + if (stream.match(/^\$\w+/)) { return "def"; } // Variables like '$RPM_BUILD_ROOT' + if (stream.match(/^\$\{\w+\}/)) { return "def"; } // Variables like '${RPM_BUILD_ROOT}' + + if (stream.match(control_flow_simple)) { return "keyword"; } + if (stream.match(control_flow_complex)) { + state.controlFlow = true; + return "keyword"; + } + if (state.controlFlow) { + if (stream.match(operators)) { return "operator"; } + if (stream.match(/^(\d+)/)) { return "number"; } + if (stream.eol()) { state.controlFlow = false; } + } + + if (stream.match(arch)) { return "number"; } + + // Macros like '%make_install' or '%attr(0775,root,root)' + if (stream.match(/^%[\w]+/)) { + if (stream.match(/^\(/)) { state.macroParameters = true; } + return "macro"; + } + if (state.macroParameters) { + if (stream.match(/^\d+/)) { return "number";} + if (stream.match(/^\)/)) { + state.macroParameters = false; + return "macro"; + } + } + if (stream.match(/^%\{\??[\w \-]+\}/)) { return "macro"; } // Macros like '%{defined fedora}' + + //TODO: Include bash script sub-parser (CodeMirror supports that) + stream.next(); + return null; + } + }; +}); + +CodeMirror.defineMIME("text/x-rpm-spec", "spec"); diff --git a/app/src/main/assets/mode/rst/rst.js b/app/src/main/assets/mode/rst/rst.js new file mode 100644 index 0000000000000000000000000000000000000000..c4ecdf4c75fc6044b805f639ec3856e2f4133794 --- /dev/null +++ b/app/src/main/assets/mode/rst/rst.js @@ -0,0 +1,326 @@ +CodeMirror.defineMode('rst', function(config, options) { + function setState(state, fn, ctx) { + state.fn = fn; + setCtx(state, ctx); + } + + function setCtx(state, ctx) { + state.ctx = ctx || {}; + } + + function setNormal(state, ch) { + if (ch && (typeof ch !== 'string')) { + var str = ch.current(); + ch = str[str.length-1]; + } + + setState(state, normal, {back: ch}); + } + + function hasMode(mode) { + if (mode) { + var modes = CodeMirror.listModes(); + + for (var i in modes) { + if (modes[i] == mode) { + return true; + } + } + } + + return false; + } + + function getMode(mode) { + if (hasMode(mode)) { + return CodeMirror.getMode(config, mode); + } else { + return null; + } + } + + var verbatimMode = getMode(options.verbatim); + var pythonMode = getMode('python'); + + var reSection = /^[!"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]/; + var reDirective = /^\s*\w([-:.\w]*\w)?::(\s|$)/; + var reHyperlink = /^\s*_[\w-]+:(\s|$)/; + var reFootnote = /^\s*\[(\d+|#)\](\s|$)/; + var reCitation = /^\s*\[[A-Za-z][\w-]*\](\s|$)/; + var reFootnoteRef = /^\[(\d+|#)\]_/; + var reCitationRef = /^\[[A-Za-z][\w-]*\]_/; + var reDirectiveMarker = /^\.\.(\s|$)/; + var reVerbatimMarker = /^::\s*$/; + var rePreInline = /^[-\s"([{/:.,;!?\\_]/; + var reEnumeratedList = /^\s*((\d+|[A-Za-z#])[.)]|\((\d+|[A-Z-a-z#])\))\s/; + var reBulletedList = /^\s*[-\+\*]\s/; + var reExamples = /^\s+(>>>|In \[\d+\]:)\s/; + + function normal(stream, state) { + var ch, sol, i; + + if (stream.eat(/\\/)) { + ch = stream.next(); + setNormal(state, ch); + return null; + } + + sol = stream.sol(); + + if (sol && (ch = stream.eat(reSection))) { + for (i = 0; stream.eat(ch); i++); + + if (i >= 3 && stream.match(/^\s*$/)) { + setNormal(state, null); + return 'header'; + } else { + stream.backUp(i + 1); + } + } + + if (sol && stream.match(reDirectiveMarker)) { + if (!stream.eol()) { + setState(state, directive); + } + return 'meta'; + } + + if (stream.match(reVerbatimMarker)) { + if (!verbatimMode) { + setState(state, verbatim); + } else { + var mode = verbatimMode; + + setState(state, verbatim, { + mode: mode, + local: mode.startState() + }); + } + return 'meta'; + } + + if (sol && stream.match(reExamples, false)) { + if (!pythonMode) { + setState(state, verbatim); + return 'meta'; + } else { + var mode = pythonMode; + + setState(state, verbatim, { + mode: mode, + local: mode.startState() + }); + + return null; + } + } + + function testBackward(re) { + return sol || !state.ctx.back || re.test(state.ctx.back); + } + + function testForward(re) { + return stream.eol() || stream.match(re, false); + } + + function testInline(re) { + return stream.match(re) && testBackward(/\W/) && testForward(/\W/); + } + + if (testInline(reFootnoteRef)) { + setNormal(state, stream); + return 'footnote'; + } + + if (testInline(reCitationRef)) { + setNormal(state, stream); + return 'citation'; + } + + ch = stream.next(); + + if (testBackward(rePreInline)) { + if ((ch === ':' || ch === '|') && stream.eat(/\S/)) { + var token; + + if (ch === ':') { + token = 'builtin'; + } else { + token = 'atom'; + } + + setState(state, inline, { + ch: ch, + wide: false, + prev: null, + token: token + }); + + return token; + } + + if (ch === '*' || ch === '`') { + var orig = ch, + wide = false; + + ch = stream.next(); + + if (ch == orig) { + wide = true; + ch = stream.next(); + } + + if (ch && !/\s/.test(ch)) { + var token; + + if (orig === '*') { + token = wide ? 'strong' : 'em'; + } else { + token = wide ? 'string' : 'string-2'; + } + + setState(state, inline, { + ch: orig, // inline() has to know what to search for + wide: wide, // are we looking for `ch` or `chch` + prev: null, // terminator must not be preceeded with whitespace + token: token // I don't want to recompute this all the time + }); + + return token; + } + } + } + + setNormal(state, ch); + return null; + } + + function inline(stream, state) { + var ch = stream.next(), + token = state.ctx.token; + + function finish(ch) { + state.ctx.prev = ch; + return token; + } + + if (ch != state.ctx.ch) { + return finish(ch); + } + + if (/\s/.test(state.ctx.prev)) { + return finish(ch); + } + + if (state.ctx.wide) { + ch = stream.next(); + + if (ch != state.ctx.ch) { + return finish(ch); + } + } + + if (!stream.eol() && !rePostInline.test(stream.peek())) { + if (state.ctx.wide) { + stream.backUp(1); + } + + return finish(ch); + } + + setState(state, normal); + setNormal(state, ch); + + return token; + } + + function directive(stream, state) { + var token = null; + + if (stream.match(reDirective)) { + token = 'attribute'; + } else if (stream.match(reHyperlink)) { + token = 'link'; + } else if (stream.match(reFootnote)) { + token = 'quote'; + } else if (stream.match(reCitation)) { + token = 'quote'; + } else { + stream.eatSpace(); + + if (stream.eol()) { + setNormal(state, stream); + return null; + } else { + stream.skipToEnd(); + setState(state, comment); + return 'comment'; + } + } + + // FIXME this is unreachable + setState(state, body, {start: true}); + return token; + } + + function body(stream, state) { + var token = 'body'; + + if (!state.ctx.start || stream.sol()) { + return block(stream, state, token); + } + + stream.skipToEnd(); + setCtx(state); + + return token; + } + + function comment(stream, state) { + return block(stream, state, 'comment'); + } + + function verbatim(stream, state) { + if (!verbatimMode) { + return block(stream, state, 'meta'); + } else { + if (stream.sol()) { + if (!stream.eatSpace()) { + setNormal(state, stream); + } + + return null; + } + + return verbatimMode.token(stream, state.ctx.local); + } + } + + function block(stream, state, token) { + if (stream.eol() || stream.eatSpace()) { + stream.skipToEnd(); + return token; + } else { + setNormal(state, stream); + return null; + } + } + + return { + startState: function() { + return {fn: normal, ctx: {}}; + }, + + copyState: function(state) { + return {fn: state.fn, ctx: state.ctx}; + }, + + token: function(stream, state) { + var token = state.fn(stream, state); + return token; + } + }; +}, "python"); + +CodeMirror.defineMIME("text/x-rst", "rst"); diff --git a/app/src/main/assets/mode/ruby/LICENSE b/app/src/main/assets/mode/ruby/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..ac09fc4035781366af9b7693ceaf19cf30deee27 --- /dev/null +++ b/app/src/main/assets/mode/ruby/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2011, Ubalo, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the Ubalo, Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL UBALO, INC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/app/src/main/assets/mode/ruby/ruby.js b/app/src/main/assets/mode/ruby/ruby.js new file mode 100644 index 0000000000000000000000000000000000000000..74ed7b96ccc0efeef7dadfc44c140ccf82e56e49 --- /dev/null +++ b/app/src/main/assets/mode/ruby/ruby.js @@ -0,0 +1,195 @@ +CodeMirror.defineMode("ruby", function(config, parserConfig) { + function wordObj(words) { + var o = {}; + for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true; + return o; + } + var keywords = wordObj([ + "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else", + "elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", "next", "not", "or", + "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless", + "until", "when", "while", "yield", "nil", "raise", "throw", "catch", "fail", "loop", "callcc", + "caller", "lambda", "proc", "public", "protected", "private", "require", "load", + "require_relative", "extend", "autoload" + ]); + var indentWords = wordObj(["def", "class", "case", "for", "while", "do", "module", "then", + "catch", "loop", "proc", "begin"]); + var dedentWords = wordObj(["end", "until"]); + var matching = {"[": "]", "{": "}", "(": ")"}; + var curPunc; + + function chain(newtok, stream, state) { + state.tokenize.push(newtok); + return newtok(stream, state); + } + + function tokenBase(stream, state) { + curPunc = null; + if (stream.sol() && stream.match("=begin") && stream.eol()) { + state.tokenize.push(readBlockComment); + return "comment"; + } + if (stream.eatSpace()) return null; + var ch = stream.next(), m; + if (ch == "`" || ch == "'" || ch == '"' || + (ch == "/" && !stream.eol() && stream.peek() != " ")) { + return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state); + } else if (ch == "%") { + var style, embed = false; + if (stream.eat("s")) style = "atom"; + else if (stream.eat(/[WQ]/)) { style = "string"; embed = true; } + else if (stream.eat(/[wxqr]/)) style = "string"; + var delim = stream.eat(/[^\w\s]/); + if (!delim) return "operator"; + if (matching.propertyIsEnumerable(delim)) delim = matching[delim]; + return chain(readQuoted(delim, style, embed, true), stream, state); + } else if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } else if (ch == "<" && (m = stream.match(/^<-?[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) { + return chain(readHereDoc(m[1]), stream, state); + } else if (ch == "0") { + if (stream.eat("x")) stream.eatWhile(/[\da-fA-F]/); + else if (stream.eat("b")) stream.eatWhile(/[01]/); + else stream.eatWhile(/[0-7]/); + return "number"; + } else if (/\d/.test(ch)) { + stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?/); + return "number"; + } else if (ch == "?") { + while (stream.match(/^\\[CM]-/)) {} + if (stream.eat("\\")) stream.eatWhile(/\w/); + else stream.next(); + return "string"; + } else if (ch == ":") { + if (stream.eat("'")) return chain(readQuoted("'", "atom", false), stream, state); + if (stream.eat('"')) return chain(readQuoted('"', "atom", true), stream, state); + stream.eatWhile(/[\w\?]/); + return "atom"; + } else if (ch == "@") { + stream.eat("@"); + stream.eatWhile(/[\w\?]/); + return "variable-2"; + } else if (ch == "$") { + stream.next(); + stream.eatWhile(/[\w\?]/); + return "variable-3"; + } else if (/\w/.test(ch)) { + stream.eatWhile(/[\w\?]/); + if (stream.eat(":")) return "atom"; + return "ident"; + } else if (ch == "|" && (state.varList || state.lastTok == "{" || state.lastTok == "do")) { + curPunc = "|"; + return null; + } else if (/[\(\)\[\]{}\\;]/.test(ch)) { + curPunc = ch; + return null; + } else if (ch == "-" && stream.eat(">")) { + return "arrow"; + } else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) { + stream.eatWhile(/[=+\-\/*:\.^%<>~|]/); + return "operator"; + } else { + return null; + } + } + + function tokenBaseUntilBrace() { + var depth = 1; + return function(stream, state) { + if (stream.peek() == "}") { + depth--; + if (depth == 0) { + state.tokenize.pop(); + return state.tokenize[state.tokenize.length-1](stream, state); + } + } else if (stream.peek() == "{") { + depth++; + } + return tokenBase(stream, state); + }; + } + function readQuoted(quote, style, embed, unescaped) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && (unescaped || !escaped)) { + state.tokenize.pop(); + break; + } + if (embed && ch == "#" && !escaped && stream.eat("{")) { + state.tokenize.push(tokenBaseUntilBrace(arguments.callee)); + break; + } + escaped = !escaped && ch == "\\"; + } + return style; + }; + } + function readHereDoc(phrase) { + return function(stream, state) { + if (stream.match(phrase)) state.tokenize.pop(); + else stream.skipToEnd(); + return "string"; + }; + } + function readBlockComment(stream, state) { + if (stream.sol() && stream.match("=end") && stream.eol()) + state.tokenize.pop(); + stream.skipToEnd(); + return "comment"; + } + + return { + startState: function() { + return {tokenize: [tokenBase], + indented: 0, + context: {type: "top", indented: -config.indentUnit}, + continuedLine: false, + lastTok: null, + varList: false}; + }, + + token: function(stream, state) { + if (stream.sol()) state.indented = stream.indentation(); + var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype; + if (style == "ident") { + var word = stream.current(); + style = keywords.propertyIsEnumerable(stream.current()) ? "keyword" + : /^[A-Z]/.test(word) ? "tag" + : (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def" + : "variable"; + if (indentWords.propertyIsEnumerable(word)) kwtype = "indent"; + else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent"; + else if ((word == "if" || word == "unless") && stream.column() == stream.indentation()) + kwtype = "indent"; + } + if (curPunc || (style && style != "comment")) state.lastTok = word || curPunc || style; + if (curPunc == "|") state.varList = !state.varList; + + if (kwtype == "indent" || /[\(\[\{]/.test(curPunc)) + state.context = {prev: state.context, type: curPunc || style, indented: state.indented}; + else if ((kwtype == "dedent" || /[\)\]\}]/.test(curPunc)) && state.context.prev) + state.context = state.context.prev; + + if (stream.eol()) + state.continuedLine = (curPunc == "\\" || style == "operator"); + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize[state.tokenize.length-1] != tokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0); + var ct = state.context; + var closing = ct.type == matching[firstChar] || + ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter); + return ct.indented + (closing ? 0 : config.indentUnit) + + (state.continuedLine ? config.indentUnit : 0); + }, + electricChars: "}de" // enD and rescuE + + }; +}); + +CodeMirror.defineMIME("text/x-ruby", "ruby"); + diff --git a/app/src/main/assets/mode/rust/rust.js b/app/src/main/assets/mode/rust/rust.js new file mode 100644 index 0000000000000000000000000000000000000000..2a5caac281d52cdfb89b6be892f2fcccea7b3797 --- /dev/null +++ b/app/src/main/assets/mode/rust/rust.js @@ -0,0 +1,432 @@ +CodeMirror.defineMode("rust", function() { + var indentUnit = 4, altIndentUnit = 2; + var valKeywords = { + "if": "if-style", "while": "if-style", "else": "else-style", + "do": "else-style", "ret": "else-style", "fail": "else-style", + "break": "atom", "cont": "atom", "const": "let", "resource": "fn", + "let": "let", "fn": "fn", "for": "for", "alt": "alt", "iface": "iface", + "impl": "impl", "type": "type", "enum": "enum", "mod": "mod", + "as": "op", "true": "atom", "false": "atom", "assert": "op", "check": "op", + "claim": "op", "native": "ignore", "unsafe": "ignore", "import": "else-style", + "export": "else-style", "copy": "op", "log": "op", "log_err": "op", + "use": "op", "bind": "op", "self": "atom" + }; + var typeKeywords = function() { + var keywords = {"fn": "fn", "block": "fn", "obj": "obj"}; + var atoms = "bool uint int i8 i16 i32 i64 u8 u16 u32 u64 float f32 f64 str char".split(" "); + for (var i = 0, e = atoms.length; i < e; ++i) keywords[atoms[i]] = "atom"; + return keywords; + }(); + var operatorChar = /[+\-*&%=<>!?|\.@]/; + + // Tokenizer + + // Used as scratch variable to communicate multiple values without + // consing up tons of objects. + var tcat, content; + function r(tc, style) { + tcat = tc; + return style; + } + + function tokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"') { + state.tokenize = tokenString; + return state.tokenize(stream, state); + } + if (ch == "'") { + tcat = "atom"; + if (stream.eat("\\")) { + if (stream.skipTo("'")) { stream.next(); return "string"; } + else { return "error"; } + } else { + stream.next(); + return stream.eat("'") ? "string" : "error"; + } + } + if (ch == "/") { + if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } + if (stream.eat("*")) { + state.tokenize = tokenComment(1); + return state.tokenize(stream, state); + } + } + if (ch == "#") { + if (stream.eat("[")) { tcat = "open-attr"; return null; } + stream.eatWhile(/\w/); + return r("macro", "meta"); + } + if (ch == ":" && stream.match(":<")) { + return r("op", null); + } + if (ch.match(/\d/) || (ch == "." && stream.eat(/\d/))) { + var flp = false; + if (!stream.match(/^x[\da-f]+/i) && !stream.match(/^b[01]+/)) { + stream.eatWhile(/\d/); + if (stream.eat(".")) { flp = true; stream.eatWhile(/\d/); } + if (stream.match(/^e[+\-]?\d+/i)) { flp = true; } + } + if (flp) stream.match(/^f(?:32|64)/); + else stream.match(/^[ui](?:8|16|32|64)/); + return r("atom", "number"); + } + if (ch.match(/[()\[\]{}:;,]/)) return r(ch, null); + if (ch == "-" && stream.eat(">")) return r("->", null); + if (ch.match(operatorChar)) { + stream.eatWhile(operatorChar); + return r("op", null); + } + stream.eatWhile(/\w/); + content = stream.current(); + if (stream.match(/^::\w/)) { + stream.backUp(1); + return r("prefix", "variable-2"); + } + if (state.keywords.propertyIsEnumerable(content)) + return r(state.keywords[content], content.match(/true|false/) ? "atom" : "keyword"); + return r("name", "variable"); + } + + function tokenString(stream, state) { + var ch, escaped = false; + while (ch = stream.next()) { + if (ch == '"' && !escaped) { + state.tokenize = tokenBase; + return r("atom", "string"); + } + escaped = !escaped && ch == "\\"; + } + // Hack to not confuse the parser when a string is split in + // pieces. + return r("op", "string"); + } + + function tokenComment(depth) { + return function(stream, state) { + var lastCh = null, ch; + while (ch = stream.next()) { + if (ch == "/" && lastCh == "*") { + if (depth == 1) { + state.tokenize = tokenBase; + break; + } else { + state.tokenize = tokenComment(depth - 1); + return state.tokenize(stream, state); + } + } + if (ch == "*" && lastCh == "/") { + state.tokenize = tokenComment(depth + 1); + return state.tokenize(stream, state); + } + lastCh = ch; + } + return "comment"; + }; + } + + // Parser + + var cx = {state: null, stream: null, marked: null, cc: null}; + function pass() { + for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); + } + function cont() { + pass.apply(null, arguments); + return true; + } + + function pushlex(type, info) { + var result = function() { + var state = cx.state; + state.lexical = {indented: state.indented, column: cx.stream.column(), + type: type, prev: state.lexical, info: info}; + }; + result.lex = true; + return result; + } + function poplex() { + var state = cx.state; + if (state.lexical.prev) { + if (state.lexical.type == ")") + state.indented = state.lexical.indented; + state.lexical = state.lexical.prev; + } + } + function typecx() { cx.state.keywords = typeKeywords; } + function valcx() { cx.state.keywords = valKeywords; } + poplex.lex = typecx.lex = valcx.lex = true; + + function commasep(comb, end) { + function more(type) { + if (type == ",") return cont(comb, more); + if (type == end) return cont(); + return cont(more); + } + return function(type) { + if (type == end) return cont(); + return pass(comb, more); + }; + } + + function stat_of(comb, tag) { + return cont(pushlex("stat", tag), comb, poplex, block); + } + function block(type) { + if (type == "}") return cont(); + if (type == "let") return stat_of(letdef1, "let"); + if (type == "fn") return stat_of(fndef); + if (type == "type") return cont(pushlex("stat"), tydef, endstatement, poplex, block); + if (type == "enum") return stat_of(enumdef); + if (type == "mod") return stat_of(mod); + if (type == "iface") return stat_of(iface); + if (type == "impl") return stat_of(impl); + if (type == "open-attr") return cont(pushlex("]"), commasep(expression, "]"), poplex); + if (type == "ignore" || type.match(/[\]\);,]/)) return cont(block); + return pass(pushlex("stat"), expression, poplex, endstatement, block); + } + function endstatement(type) { + if (type == ";") return cont(); + return pass(); + } + function expression(type) { + if (type == "atom" || type == "name") return cont(maybeop); + if (type == "{") return cont(pushlex("}"), exprbrace, poplex); + if (type.match(/[\[\(]/)) return matchBrackets(type, expression); + if (type.match(/[\]\)\};,]/)) return pass(); + if (type == "if-style") return cont(expression, expression); + if (type == "else-style" || type == "op") return cont(expression); + if (type == "for") return cont(pattern, maybetype, inop, expression, expression); + if (type == "alt") return cont(expression, altbody); + if (type == "fn") return cont(fndef); + if (type == "macro") return cont(macro); + return cont(); + } + function maybeop(type) { + if (content == ".") return cont(maybeprop); + if (content == "::<"){return cont(typarams, maybeop);} + if (type == "op" || content == ":") return cont(expression); + if (type == "(" || type == "[") return matchBrackets(type, expression); + return pass(); + } + function maybeprop(type) { + if (content.match(/^\w+$/)) {cx.marked = "variable"; return cont(maybeop);} + return pass(expression); + } + function exprbrace(type) { + if (type == "op") { + if (content == "|") return cont(blockvars, poplex, pushlex("}", "block"), block); + if (content == "||") return cont(poplex, pushlex("}", "block"), block); + } + if (content == "mutable" || (content.match(/^\w+$/) && cx.stream.peek() == ":" + && !cx.stream.match("::", false))) + return pass(record_of(expression)); + return pass(block); + } + function record_of(comb) { + function ro(type) { + if (content == "mutable" || content == "with") {cx.marked = "keyword"; return cont(ro);} + if (content.match(/^\w*$/)) {cx.marked = "variable"; return cont(ro);} + if (type == ":") return cont(comb, ro); + if (type == "}") return cont(); + return cont(ro); + } + return ro; + } + function blockvars(type) { + if (type == "name") {cx.marked = "def"; return cont(blockvars);} + if (type == "op" && content == "|") return cont(); + return cont(blockvars); + } + + function letdef1(type) { + if (type.match(/[\]\)\};]/)) return cont(); + if (content == "=") return cont(expression, letdef2); + if (type == ",") return cont(letdef1); + return pass(pattern, maybetype, letdef1); + } + function letdef2(type) { + if (type.match(/[\]\)\};,]/)) return pass(letdef1); + else return pass(expression, letdef2); + } + function maybetype(type) { + if (type == ":") return cont(typecx, rtype, valcx); + return pass(); + } + function inop(type) { + if (type == "name" && content == "in") {cx.marked = "keyword"; return cont();} + return pass(); + } + function fndef(type) { + if (content == "@" || content == "~") {cx.marked = "keyword"; return cont(fndef);} + if (type == "name") {cx.marked = "def"; return cont(fndef);} + if (content == "<") return cont(typarams, fndef); + if (type == "{") return pass(expression); + if (type == "(") return cont(pushlex(")"), commasep(argdef, ")"), poplex, fndef); + if (type == "->") return cont(typecx, rtype, valcx, fndef); + if (type == ";") return cont(); + return cont(fndef); + } + function tydef(type) { + if (type == "name") {cx.marked = "def"; return cont(tydef);} + if (content == "<") return cont(typarams, tydef); + if (content == "=") return cont(typecx, rtype, valcx); + return cont(tydef); + } + function enumdef(type) { + if (type == "name") {cx.marked = "def"; return cont(enumdef);} + if (content == "<") return cont(typarams, enumdef); + if (content == "=") return cont(typecx, rtype, valcx, endstatement); + if (type == "{") return cont(pushlex("}"), typecx, enumblock, valcx, poplex); + return cont(enumdef); + } + function enumblock(type) { + if (type == "}") return cont(); + if (type == "(") return cont(pushlex(")"), commasep(rtype, ")"), poplex, enumblock); + if (content.match(/^\w+$/)) cx.marked = "def"; + return cont(enumblock); + } + function mod(type) { + if (type == "name") {cx.marked = "def"; return cont(mod);} + if (type == "{") return cont(pushlex("}"), block, poplex); + return pass(); + } + function iface(type) { + if (type == "name") {cx.marked = "def"; return cont(iface);} + if (content == "<") return cont(typarams, iface); + if (type == "{") return cont(pushlex("}"), block, poplex); + return pass(); + } + function impl(type) { + if (content == "<") return cont(typarams, impl); + if (content == "of" || content == "for") {cx.marked = "keyword"; return cont(rtype, impl);} + if (type == "name") {cx.marked = "def"; return cont(impl);} + if (type == "{") return cont(pushlex("}"), block, poplex); + return pass(); + } + function typarams(type) { + if (content == ">") return cont(); + if (content == ",") return cont(typarams); + if (content == ":") return cont(rtype, typarams); + return pass(rtype, typarams); + } + function argdef(type) { + if (type == "name") {cx.marked = "def"; return cont(argdef);} + if (type == ":") return cont(typecx, rtype, valcx); + return pass(); + } + function rtype(type) { + if (type == "name") {cx.marked = "variable-3"; return cont(rtypemaybeparam); } + if (content == "mutable") {cx.marked = "keyword"; return cont(rtype);} + if (type == "atom") return cont(rtypemaybeparam); + if (type == "op" || type == "obj") return cont(rtype); + if (type == "fn") return cont(fntype); + if (type == "{") return cont(pushlex("{"), record_of(rtype), poplex); + return matchBrackets(type, rtype); + } + function rtypemaybeparam(type) { + if (content == "<") return cont(typarams); + return pass(); + } + function fntype(type) { + if (type == "(") return cont(pushlex("("), commasep(rtype, ")"), poplex, fntype); + if (type == "->") return cont(rtype); + return pass(); + } + function pattern(type) { + if (type == "name") {cx.marked = "def"; return cont(patternmaybeop);} + if (type == "atom") return cont(patternmaybeop); + if (type == "op") return cont(pattern); + if (type.match(/[\]\)\};,]/)) return pass(); + return matchBrackets(type, pattern); + } + function patternmaybeop(type) { + if (type == "op" && content == ".") return cont(); + if (content == "to") {cx.marked = "keyword"; return cont(pattern);} + else return pass(); + } + function altbody(type) { + if (type == "{") return cont(pushlex("}", "alt"), altblock1, poplex); + return pass(); + } + function altblock1(type) { + if (type == "}") return cont(); + if (type == "|") return cont(altblock1); + if (content == "when") {cx.marked = "keyword"; return cont(expression, altblock2);} + if (type.match(/[\]\);,]/)) return cont(altblock1); + return pass(pattern, altblock2); + } + function altblock2(type) { + if (type == "{") return cont(pushlex("}", "alt"), block, poplex, altblock1); + else return pass(altblock1); + } + + function macro(type) { + if (type.match(/[\[\(\{]/)) return matchBrackets(type, expression); + return pass(); + } + function matchBrackets(type, comb) { + if (type == "[") return cont(pushlex("]"), commasep(comb, "]"), poplex); + if (type == "(") return cont(pushlex(")"), commasep(comb, ")"), poplex); + if (type == "{") return cont(pushlex("}"), commasep(comb, "}"), poplex); + return cont(); + } + + function parse(state, stream, style) { + var cc = state.cc; + // Communicate our context to the combinators. + // (Less wasteful than consing up a hundred closures on every call.) + cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; + + while (true) { + var combinator = cc.length ? cc.pop() : block; + if (combinator(tcat)) { + while(cc.length && cc[cc.length - 1].lex) + cc.pop()(); + return cx.marked || style; + } + } + } + + return { + startState: function() { + return { + tokenize: tokenBase, + cc: [], + lexical: {indented: -indentUnit, column: 0, type: "top", align: false}, + keywords: valKeywords, + indented: 0 + }; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = false; + state.indented = stream.indentation(); + } + if (stream.eatSpace()) return null; + tcat = content = null; + var style = state.tokenize(stream, state); + if (style == "comment") return style; + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = true; + if (tcat == "prefix") return style; + if (!content) content = stream.current(); + return parse(state, stream, style); + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, + type = lexical.type, closing = firstChar == type; + if (type == "stat") return lexical.indented + indentUnit; + if (lexical.align) return lexical.column + (closing ? 0 : 1); + return lexical.indented + (closing ? 0 : (lexical.info == "alt" ? altIndentUnit : indentUnit)); + }, + + electricChars: "{}" + }; +}); + +CodeMirror.defineMIME("text/x-rustsrc", "rust"); diff --git a/app/src/main/assets/mode/scheme/scheme.js b/app/src/main/assets/mode/scheme/scheme.js new file mode 100644 index 0000000000000000000000000000000000000000..2411db07990a6db139e57b9876096995cf6c363a --- /dev/null +++ b/app/src/main/assets/mode/scheme/scheme.js @@ -0,0 +1,230 @@ +/** + * Author: Koh Zi Han, based on implementation by Koh Zi Chun + */ +CodeMirror.defineMode("scheme", function (config, mode) { + var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", + ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD="keyword"; + var INDENT_WORD_SKIP = 2, KEYWORDS_SKIP = 1; + + function makeKeywords(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var keywords = makeKeywords("λ case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt #f floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string=? string>? string? substring symbol->string symbol? #t tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?"); + var indentKeys = makeKeywords("define let letrec let* lambda"); + + function stateStack(indent, type, prev) { // represents a state stack object + this.indent = indent; + this.type = type; + this.prev = prev; + } + + function pushStack(state, indent, type) { + state.indentStack = new stateStack(indent, type, state.indentStack); + } + + function popStack(state) { + state.indentStack = state.indentStack.prev; + } + + var binaryMatcher = new RegExp(/^(?:[-+]i|[-+][01]+#*(?:\/[01]+#*)?i|[-+]?[01]+#*(?:\/[01]+#*)?@[-+]?[01]+#*(?:\/[01]+#*)?|[-+]?[01]+#*(?:\/[01]+#*)?[-+](?:[01]+#*(?:\/[01]+#*)?)?i|[-+]?[01]+#*(?:\/[01]+#*)?)(?=[()\s;"]|$)/i); + var octalMatcher = new RegExp(/^(?:[-+]i|[-+][0-7]+#*(?:\/[0-7]+#*)?i|[-+]?[0-7]+#*(?:\/[0-7]+#*)?@[-+]?[0-7]+#*(?:\/[0-7]+#*)?|[-+]?[0-7]+#*(?:\/[0-7]+#*)?[-+](?:[0-7]+#*(?:\/[0-7]+#*)?)?i|[-+]?[0-7]+#*(?:\/[0-7]+#*)?)(?=[()\s;"]|$)/i); + var hexMatcher = new RegExp(/^(?:[-+]i|[-+][\da-f]+#*(?:\/[\da-f]+#*)?i|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?@[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?[-+](?:[\da-f]+#*(?:\/[\da-f]+#*)?)?i|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?)(?=[()\s;"]|$)/i); + var decimalMatcher = new RegExp(/^(?:[-+]i|[-+](?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)i|[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)@[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)|[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)[-+](?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)?i|(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*))(?=[()\s;"]|$)/i); + + function isBinaryNumber (stream) { + return stream.match(binaryMatcher); + } + + function isOctalNumber (stream) { + return stream.match(octalMatcher); + } + + function isDecimalNumber (stream, backup) { + if (backup === true) { + stream.backUp(1); + } + return stream.match(decimalMatcher); + } + + function isHexNumber (stream) { + return stream.match(hexMatcher); + } + + return { + startState: function () { + return { + indentStack: null, + indentation: 0, + mode: false, + sExprComment: false + }; + }, + + token: function (stream, state) { + if (state.indentStack == null && stream.sol()) { + // update indentation, but only if indentStack is empty + state.indentation = stream.indentation(); + } + + // skip spaces + if (stream.eatSpace()) { + return null; + } + var returnType = null; + + switch(state.mode){ + case "string": // multi-line string parsing mode + var next, escaped = false; + while ((next = stream.next()) != null) { + if (next == "\"" && !escaped) { + + state.mode = false; + break; + } + escaped = !escaped && next == "\\"; + } + returnType = STRING; // continue on in scheme-string mode + break; + case "comment": // comment parsing mode + var next, maybeEnd = false; + while ((next = stream.next()) != null) { + if (next == "#" && maybeEnd) { + + state.mode = false; + break; + } + maybeEnd = (next == "|"); + } + returnType = COMMENT; + break; + case "s-expr-comment": // s-expr commenting mode + state.mode = false; + if(stream.peek() == "(" || stream.peek() == "["){ + // actually start scheme s-expr commenting mode + state.sExprComment = 0; + }else{ + // if not we just comment the entire of the next token + stream.eatWhile(/[^/s]/); // eat non spaces + returnType = COMMENT; + break; + } + default: // default parsing mode + var ch = stream.next(); + + if (ch == "\"") { + state.mode = "string"; + returnType = STRING; + + } else if (ch == "'") { + returnType = ATOM; + } else if (ch == '#') { + if (stream.eat("|")) { // Multi-line comment + state.mode = "comment"; // toggle to comment mode + returnType = COMMENT; + } else if (stream.eat(/[tf]/i)) { // #t/#f (atom) + returnType = ATOM; + } else if (stream.eat(';')) { // S-Expr comment + state.mode = "s-expr-comment"; + returnType = COMMENT; + } else { + var numTest = null, hasExactness = false, hasRadix = true; + if (stream.eat(/[ei]/i)) { + hasExactness = true; + } else { + stream.backUp(1); // must be radix specifier + } + if (stream.match(/^#b/i)) { + numTest = isBinaryNumber; + } else if (stream.match(/^#o/i)) { + numTest = isOctalNumber; + } else if (stream.match(/^#x/i)) { + numTest = isHexNumber; + } else if (stream.match(/^#d/i)) { + numTest = isDecimalNumber; + } else if (stream.match(/^[-+0-9.]/, false)) { + hasRadix = false; + numTest = isDecimalNumber; + // re-consume the intial # if all matches failed + } else if (!hasExactness) { + stream.eat('#'); + } + if (numTest != null) { + if (hasRadix && !hasExactness) { + // consume optional exactness after radix + stream.match(/^#[ei]/i); + } + if (numTest(stream)) + returnType = NUMBER; + } + } + } else if (/^[-+0-9.]/.test(ch) && isDecimalNumber(stream, true)) { // match non-prefixed number, must be decimal + returnType = NUMBER; + } else if (ch == ";") { // comment + stream.skipToEnd(); // rest of the line is a comment + returnType = COMMENT; + } else if (ch == "(" || ch == "[") { + var keyWord = ''; var indentTemp = stream.column(), letter; + /** + Either + (indent-word .. + (non-indent-word .. + (;something else, bracket, etc. + */ + + while ((letter = stream.eat(/[^\s\(\[\;\)\]]/)) != null) { + keyWord += letter; + } + + if (keyWord.length > 0 && indentKeys.propertyIsEnumerable(keyWord)) { // indent-word + + pushStack(state, indentTemp + INDENT_WORD_SKIP, ch); + } else { // non-indent word + // we continue eating the spaces + stream.eatSpace(); + if (stream.eol() || stream.peek() == ";") { + // nothing significant after + // we restart indentation 1 space after + pushStack(state, indentTemp + 1, ch); + } else { + pushStack(state, indentTemp + stream.current().length, ch); // else we match + } + } + stream.backUp(stream.current().length - 1); // undo all the eating + + if(typeof state.sExprComment == "number") state.sExprComment++; + + returnType = BRACKET; + } else if (ch == ")" || ch == "]") { + returnType = BRACKET; + if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : "[")) { + popStack(state); + + if(typeof state.sExprComment == "number"){ + if(--state.sExprComment == 0){ + returnType = COMMENT; // final closing bracket + state.sExprComment = false; // turn off s-expr commenting mode + } + } + } + } else { + stream.eatWhile(/[\w\$_\-!$%&*+\.\/:<=>?@\^~]/); + + if (keywords && keywords.propertyIsEnumerable(stream.current())) { + returnType = BUILTIN; + } else returnType = "variable"; + } + } + return (typeof state.sExprComment == "number") ? COMMENT : returnType; + }, + + indent: function (state, textAfter) { + if (state.indentStack == null) return state.indentation; + return state.indentStack.indent; + } + }; +}); + +CodeMirror.defineMIME("text/x-scheme", "scheme"); diff --git a/app/src/main/assets/mode/shell/shell.js b/app/src/main/assets/mode/shell/shell.js new file mode 100644 index 0000000000000000000000000000000000000000..d4eba852ba76ac84a691bc99b0a57a8bab4b45a0 --- /dev/null +++ b/app/src/main/assets/mode/shell/shell.js @@ -0,0 +1,118 @@ +CodeMirror.defineMode('shell', function(config) { + + var words = {}; + function define(style, string) { + var split = string.split(' '); + for(var i = 0; i < split.length; i++) { + words[split[i]] = style; + } + }; + + // Atoms + define('atom', 'true false'); + + // Keywords + define('keyword', 'if then do else elif while until for in esac fi fin ' + + 'fil done exit set unset export function'); + + // Commands + define('builtin', 'ab awk bash beep cat cc cd chown chmod chroot clear cp ' + + 'curl cut diff echo find gawk gcc get git grep kill killall ln ls make ' + + 'mkdir openssl mv nc node npm ping ps restart rm rmdir sed service sh ' + + 'shopt shred source sort sleep ssh start stop su sudo tee telnet top ' + + 'touch vi vim wall wc wget who write yes zsh'); + + function tokenBase(stream, state) { + + var sol = stream.sol(); + var ch = stream.next(); + + if (ch === '\'' || ch === '"' || ch === '`') { + state.tokens.unshift(tokenString(ch)); + return tokenize(stream, state); + } + if (ch === '#') { + if (sol && stream.eat('!')) { + stream.skipToEnd(); + return 'meta'; // 'comment'? + } + stream.skipToEnd(); + return 'comment'; + } + if (ch === '$') { + state.tokens.unshift(tokenDollar); + return tokenize(stream, state); + } + if (ch === '+' || ch === '=') { + return 'operator'; + } + if (ch === '-') { + stream.eat('-'); + stream.eatWhile(/\w/); + return 'attribute'; + } + if (/\d/.test(ch)) { + stream.eatWhile(/\d/); + if(!/\w/.test(stream.peek())) { + return 'number'; + } + } + stream.eatWhile(/\w/); + var cur = stream.current(); + if (stream.peek() === '=' && /\w+/.test(cur)) return 'def'; + return words.hasOwnProperty(cur) ? words[cur] : null; + } + + function tokenString(quote) { + return function(stream, state) { + var next, end = false, escaped = false; + while ((next = stream.next()) != null) { + if (next === quote && !escaped) { + end = true; + break; + } + if (next === '$' && !escaped && quote !== '\'') { + escaped = true; + stream.backUp(1); + state.tokens.unshift(tokenDollar); + break; + } + escaped = !escaped && next === '\\'; + } + if (end || !escaped) { + state.tokens.shift(); + } + return (quote === '`' || quote === ')' ? 'quote' : 'string'); + }; + }; + + var tokenDollar = function(stream, state) { + if (state.tokens.length > 1) stream.eat('$'); + var ch = stream.next(), hungry = /\w/; + if (ch === '{') hungry = /[^}]/; + if (ch === '(') { + state.tokens[0] = tokenString(')'); + return tokenize(stream, state); + } + if (!/\d/.test(ch)) { + stream.eatWhile(hungry); + stream.eat('}'); + } + state.tokens.shift(); + return 'def'; + }; + + function tokenize(stream, state) { + return (state.tokens[0] || tokenBase) (stream, state); + }; + + return { + startState: function() {return {tokens:[]};}, + token: function(stream, state) { + if (stream.eatSpace()) return null; + return tokenize(stream, state); + } + }; +}); + +CodeMirror.defineMIME('text/x-sh', 'shell'); diff --git a/app/src/main/assets/mode/sieve/LICENSE b/app/src/main/assets/mode/sieve/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..24e4c94c098cc90130be055c6770ab89ba2feed7 --- /dev/null +++ b/app/src/main/assets/mode/sieve/LICENSE @@ -0,0 +1,23 @@ +Copyright (C) 2012 Thomas Schmid + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +Please note that some subdirectories of the CodeMirror distribution +include their own LICENSE files, and are released under different +licences. diff --git a/app/src/main/assets/mode/sieve/sieve.js b/app/src/main/assets/mode/sieve/sieve.js new file mode 100644 index 0000000000000000000000000000000000000000..db777c1316e3ced801ef6e6d020194237ee947b5 --- /dev/null +++ b/app/src/main/assets/mode/sieve/sieve.js @@ -0,0 +1,156 @@ +/* + * See LICENSE in this directory for the license under which this code + * is released. + */ + +CodeMirror.defineMode("sieve", function(config) { + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var keywords = words("if elsif else stop require"); + var atoms = words("true false not"); + var indentUnit = config.indentUnit; + + function tokenBase(stream, state) { + + var ch = stream.next(); + if (ch == "/" && stream.eat("*")) { + state.tokenize = tokenCComment; + return tokenCComment(stream, state); + } + + if (ch === '#') { + stream.skipToEnd(); + return "comment"; + } + + if (ch == "\"") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + + if (ch === "{") + { + state._indent++; + return null; + } + + if (ch === "}") + { + state._indent--; + return null; + } + + if (/[{}\(\),;]/.test(ch)) + return null; + + // 1*DIGIT "K" / "M" / "G" + if (/\d/.test(ch)) { + stream.eatWhile(/[\d]/); + stream.eat(/[KkMmGg]/); + return "number"; + } + + // ":" (ALPHA / "_") *(ALPHA / DIGIT / "_") + if (ch == ":") { + stream.eatWhile(/[a-zA-Z_]/); + stream.eatWhile(/[a-zA-Z0-9_]/); + + return "operator"; + } + + stream.eatWhile(/[\w\$_]/); + var cur = stream.current(); + + // "text:" *(SP / HTAB) (hash-comment / CRLF) + // *(multiline-literal / multiline-dotstart) + // "." CRLF + if ((cur == "text") && stream.eat(":")) + { + state.tokenize = tokenMultiLineString; + return "string"; + } + + if (keywords.propertyIsEnumerable(cur)) + return "keyword"; + + if (atoms.propertyIsEnumerable(cur)) + return "atom"; + } + + function tokenMultiLineString(stream, state) + { + state._multiLineString = true; + // the first line is special it may contain a comment + if (!stream.sol()) { + stream.eatSpace(); + + if (stream.peek() == "#") { + stream.skipToEnd(); + return "comment"; + } + + stream.skipToEnd(); + return "string"; + } + + if ((stream.next() == ".") && (stream.eol())) + { + state._multiLineString = false; + state.tokenize = tokenBase; + } + + return "string"; + } + + function tokenCComment(stream, state) { + var maybeEnd = false, ch; + while ((ch = stream.next()) != null) { + if (maybeEnd && ch == "/") { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) + break; + escaped = !escaped && ch == "\\"; + } + if (!escaped) state.tokenize = tokenBase; + return "string"; + }; + } + + return { + startState: function(base) { + return {tokenize: tokenBase, + baseIndent: base || 0, + _indent: 0}; + }, + + token: function(stream, state) { + if (stream.eatSpace()) + return null; + + return (state.tokenize || tokenBase)(stream, state);; + }, + + indent: function(state, textAfter) { + return state.baseIndent + state._indent * indentUnit; + }, + + electricChars: "}" + }; +}); + +CodeMirror.defineMIME("application/sieve", "sieve"); diff --git a/app/src/main/assets/mode/smalltalk/smalltalk.js b/app/src/main/assets/mode/smalltalk/smalltalk.js new file mode 100644 index 0000000000000000000000000000000000000000..ba17cbdc982b803ab35033e71c1be11d71119825 --- /dev/null +++ b/app/src/main/assets/mode/smalltalk/smalltalk.js @@ -0,0 +1,139 @@ +CodeMirror.defineMode('smalltalk', function(config, modeConfig) { + + var specialChars = /[+\-/\\*~<>=@%|&?!.:;^]/; + var keywords = /true|false|nil|self|super|thisContext/; + + var Context = function(tokenizer, parent) { + this.next = tokenizer; + this.parent = parent; + }; + + var Token = function(name, context, eos) { + this.name = name; + this.context = context; + this.eos = eos; + }; + + var State = function() { + this.context = new Context(next, null); + this.expectVariable = true; + this.indentation = 0; + this.userIndentationDelta = 0; + }; + + State.prototype.userIndent = function(indentation) { + this.userIndentationDelta = indentation > 0 ? (indentation / config.indentUnit - this.indentation) : 0; + }; + + var next = function(stream, context, state) { + var token = new Token(null, context, false); + var aChar = stream.next(); + + if (aChar === '"') { + token = nextComment(stream, new Context(nextComment, context)); + + } else if (aChar === '\'') { + token = nextString(stream, new Context(nextString, context)); + + } else if (aChar === '#') { + stream.eatWhile(/[^ .]/); + token.name = 'string-2'; + + } else if (aChar === '$') { + stream.eatWhile(/[^ ]/); + token.name = 'string-2'; + + } else if (aChar === '|' && state.expectVariable) { + token.context = new Context(nextTemporaries, context); + + } else if (/[\[\]{}()]/.test(aChar)) { + token.name = 'bracket'; + token.eos = /[\[{(]/.test(aChar); + + if (aChar === '[') { + state.indentation++; + } else if (aChar === ']') { + state.indentation = Math.max(0, state.indentation - 1); + } + + } else if (specialChars.test(aChar)) { + stream.eatWhile(specialChars); + token.name = 'operator'; + token.eos = aChar !== ';'; // ; cascaded message expression + + } else if (/\d/.test(aChar)) { + stream.eatWhile(/[\w\d]/); + token.name = 'number'; + + } else if (/[\w_]/.test(aChar)) { + stream.eatWhile(/[\w\d_]/); + token.name = state.expectVariable ? (keywords.test(stream.current()) ? 'keyword' : 'variable') : null; + + } else { + token.eos = state.expectVariable; + } + + return token; + }; + + var nextComment = function(stream, context) { + stream.eatWhile(/[^"]/); + return new Token('comment', stream.eat('"') ? context.parent : context, true); + }; + + var nextString = function(stream, context) { + stream.eatWhile(/[^']/); + return new Token('string', stream.eat('\'') ? context.parent : context, false); + }; + + var nextTemporaries = function(stream, context, state) { + var token = new Token(null, context, false); + var aChar = stream.next(); + + if (aChar === '|') { + token.context = context.parent; + token.eos = true; + + } else { + stream.eatWhile(/[^|]/); + token.name = 'variable'; + } + + return token; + }; + + return { + startState: function() { + return new State; + }, + + token: function(stream, state) { + state.userIndent(stream.indentation()); + + if (stream.eatSpace()) { + return null; + } + + var token = state.context.next(stream, state.context, state); + state.context = token.context; + state.expectVariable = token.eos; + + state.lastToken = token; + return token.name; + }, + + blankLine: function(state) { + state.userIndent(0); + }, + + indent: function(state, textAfter) { + var i = state.context.next === next && textAfter && textAfter.charAt(0) === ']' ? -1 : state.userIndentationDelta; + return (state.indentation + i) * config.indentUnit; + }, + + electricChars: ']' + }; + +}); + +CodeMirror.defineMIME('text/x-stsrc', {name: 'smalltalk'}); \ No newline at end of file diff --git a/app/src/main/assets/mode/smarty/smarty.js b/app/src/main/assets/mode/smarty/smarty.js new file mode 100644 index 0000000000000000000000000000000000000000..941e7f374e337517440fafeb62ab8e80d1991281 --- /dev/null +++ b/app/src/main/assets/mode/smarty/smarty.js @@ -0,0 +1,148 @@ +CodeMirror.defineMode("smarty", function(config, parserConfig) { + var keyFuncs = ["debug", "extends", "function", "include", "literal"]; + var last; + var regs = { + operatorChars: /[+\-*&%=<>!?]/, + validIdentifier: /[a-zA-Z0-9\_]/, + stringChar: /[\'\"]/ + }; + var leftDelim = (typeof config.mode.leftDelimiter != 'undefined') ? config.mode.leftDelimiter : "{"; + var rightDelim = (typeof config.mode.rightDelimiter != 'undefined') ? config.mode.rightDelimiter : "}"; + function ret(style, lst) { last = lst; return style; } + + + function tokenizer(stream, state) { + function chain(parser) { + state.tokenize = parser; + return parser(stream, state); + } + + if (stream.match(leftDelim, true)) { + if (stream.eat("*")) { + return chain(inBlock("comment", "*" + rightDelim)); + } + else { + state.tokenize = inSmarty; + return "tag"; + } + } + else { + // I'd like to do an eatWhile() here, but I can't get it to eat only up to the rightDelim string/char + stream.next(); + return null; + } + } + + function inSmarty(stream, state) { + if (stream.match(rightDelim, true)) { + state.tokenize = tokenizer; + return ret("tag", null); + } + + var ch = stream.next(); + if (ch == "$") { + stream.eatWhile(regs.validIdentifier); + return ret("variable-2", "variable"); + } + else if (ch == ".") { + return ret("operator", "property"); + } + else if (regs.stringChar.test(ch)) { + state.tokenize = inAttribute(ch); + return ret("string", "string"); + } + else if (regs.operatorChars.test(ch)) { + stream.eatWhile(regs.operatorChars); + return ret("operator", "operator"); + } + else if (ch == "[" || ch == "]") { + return ret("bracket", "bracket"); + } + else if (/\d/.test(ch)) { + stream.eatWhile(/\d/); + return ret("number", "number"); + } + else { + if (state.last == "variable") { + if (ch == "@") { + stream.eatWhile(regs.validIdentifier); + return ret("property", "property"); + } + else if (ch == "|") { + stream.eatWhile(regs.validIdentifier); + return ret("qualifier", "modifier"); + } + } + else if (state.last == "whitespace") { + stream.eatWhile(regs.validIdentifier); + return ret("attribute", "modifier"); + } + else if (state.last == "property") { + stream.eatWhile(regs.validIdentifier); + return ret("property", null); + } + else if (/\s/.test(ch)) { + last = "whitespace"; + return null; + } + + var str = ""; + if (ch != "/") { + str += ch; + } + var c = ""; + while ((c = stream.eat(regs.validIdentifier))) { + str += c; + } + var i, j; + for (i=0, j=keyFuncs.length; i=&|]/; + + function tokenBase(stream, state) { + var ch = stream.next(); + curPunc = null; + if (ch == "$" || ch == "?") { + stream.match(/^[\w\d]*/); + return "variable-2"; + } + else if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) { + stream.match(/^[^\s\u00a0>]*>?/); + return "atom"; + } + else if (ch == "\"" || ch == "'") { + state.tokenize = tokenLiteral(ch); + return state.tokenize(stream, state); + } + else if (/[{}\(\),\.;\[\]]/.test(ch)) { + curPunc = ch; + return null; + } + else if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } + else if (operatorChars.test(ch)) { + stream.eatWhile(operatorChars); + return null; + } + else if (ch == ":") { + stream.eatWhile(/[\w\d\._\-]/); + return "atom"; + } + else { + stream.eatWhile(/[_\w\d]/); + if (stream.eat(":")) { + stream.eatWhile(/[\w\d_\-]/); + return "atom"; + } + var word = stream.current(), type; + if (ops.test(word)) + return null; + else if (keywords.test(word)) + return "keyword"; + else + return "variable"; + } + } + + function tokenLiteral(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) { + state.tokenize = tokenBase; + break; + } + escaped = !escaped && ch == "\\"; + } + return "string"; + }; + } + + function pushContext(state, type, col) { + state.context = {prev: state.context, indent: state.indent, col: col, type: type}; + } + function popContext(state) { + state.indent = state.context.indent; + state.context = state.context.prev; + } + + return { + startState: function(base) { + return {tokenize: tokenBase, + context: null, + indent: 0, + col: 0}; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (state.context && state.context.align == null) state.context.align = false; + state.indent = stream.indentation(); + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + + if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") { + state.context.align = true; + } + + if (curPunc == "(") pushContext(state, ")", stream.column()); + else if (curPunc == "[") pushContext(state, "]", stream.column()); + else if (curPunc == "{") pushContext(state, "}", stream.column()); + else if (/[\]\}\)]/.test(curPunc)) { + while (state.context && state.context.type == "pattern") popContext(state); + if (state.context && curPunc == state.context.type) popContext(state); + } + else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state); + else if (/atom|string|variable/.test(style) && state.context) { + if (/[\}\]]/.test(state.context.type)) + pushContext(state, "pattern", stream.column()); + else if (state.context.type == "pattern" && !state.context.align) { + state.context.align = true; + state.context.col = stream.column(); + } + } + + return style; + }, + + indent: function(state, textAfter) { + var firstChar = textAfter && textAfter.charAt(0); + var context = state.context; + if (/[\]\}]/.test(firstChar)) + while (context && context.type == "pattern") context = context.prev; + + var closing = context && firstChar == context.type; + if (!context) + return 0; + else if (context.type == "pattern") + return context.col; + else if (context.align) + return context.col + (closing ? 0 : 1); + else + return context.indent + (closing ? 0 : indentUnit); + } + }; +}); + +CodeMirror.defineMIME("application/x-sparql-query", "sparql"); diff --git a/app/src/main/assets/mode/stex/stex.js b/app/src/main/assets/mode/stex/stex.js new file mode 100644 index 0000000000000000000000000000000000000000..100854dab39d578ec796c37fc7d322d2936141cd --- /dev/null +++ b/app/src/main/assets/mode/stex/stex.js @@ -0,0 +1,182 @@ +/* + * Author: Constantin Jucovschi (c.jucovschi@jacobs-university.de) + * Licence: MIT + */ + +CodeMirror.defineMode("stex", function(cmCfg, modeCfg) +{ + function pushCommand(state, command) { + state.cmdState.push(command); + } + + function peekCommand(state) { + if (state.cmdState.length>0) + return state.cmdState[state.cmdState.length-1]; + else + return null; + } + + function popCommand(state) { + if (state.cmdState.length>0) { + var plug = state.cmdState.pop(); + plug.closeBracket(); + } + } + + function applyMostPowerful(state) { + var context = state.cmdState; + for (var i = context.length - 1; i >= 0; i--) { + var plug = context[i]; + if (plug.name=="DEFAULT") + continue; + return plug.styleIdentifier(); + } + return null; + } + + function addPluginPattern(pluginName, cmdStyle, brackets, styles) { + return function () { + this.name=pluginName; + this.bracketNo = 0; + this.style=cmdStyle; + this.styles = styles; + this.brackets = brackets; + + this.styleIdentifier = function(content) { + if (this.bracketNo<=this.styles.length) + return this.styles[this.bracketNo-1]; + else + return null; + }; + this.openBracket = function(content) { + this.bracketNo++; + return "bracket"; + }; + this.closeBracket = function(content) { + }; + }; + } + + var plugins = new Array(); + + plugins["importmodule"] = addPluginPattern("importmodule", "tag", "{[", ["string", "builtin"]); + plugins["documentclass"] = addPluginPattern("documentclass", "tag", "{[", ["", "atom"]); + plugins["usepackage"] = addPluginPattern("documentclass", "tag", "[", ["atom"]); + plugins["begin"] = addPluginPattern("documentclass", "tag", "[", ["atom"]); + plugins["end"] = addPluginPattern("documentclass", "tag", "[", ["atom"]); + + plugins["DEFAULT"] = function () { + this.name="DEFAULT"; + this.style="tag"; + + this.styleIdentifier = function(content) { + }; + this.openBracket = function(content) { + }; + this.closeBracket = function(content) { + }; + }; + + function setState(state, f) { + state.f = f; + } + + function normal(source, state) { + if (source.match(/^\\[a-zA-Z@]+/)) { + var cmdName = source.current(); + cmdName = cmdName.substr(1, cmdName.length-1); + var plug; + if (plugins.hasOwnProperty(cmdName)) { + plug = plugins[cmdName]; + } else { + plug = plugins["DEFAULT"]; + } + plug = new plug(); + pushCommand(state, plug); + setState(state, beginParams); + return plug.style; + } + + // escape characters + if (source.match(/^\\[$&%#{}_]/)) { + return "tag"; + } + + // white space control characters + if (source.match(/^\\[,;!\/]/)) { + return "tag"; + } + + var ch = source.next(); + if (ch == "%") { + // special case: % at end of its own line; stay in same state + if (!source.eol()) { + setState(state, inCComment); + } + return "comment"; + } + else if (ch=='}' || ch==']') { + plug = peekCommand(state); + if (plug) { + plug.closeBracket(ch); + setState(state, beginParams); + } else + return "error"; + return "bracket"; + } else if (ch=='{' || ch=='[') { + plug = plugins["DEFAULT"]; + plug = new plug(); + pushCommand(state, plug); + return "bracket"; + } + else if (/\d/.test(ch)) { + source.eatWhile(/[\w.%]/); + return "atom"; + } + else { + source.eatWhile(/[\w-_]/); + return applyMostPowerful(state); + } + } + + function inCComment(source, state) { + source.skipToEnd(); + setState(state, normal); + return "comment"; + } + + function beginParams(source, state) { + var ch = source.peek(); + if (ch == '{' || ch == '[') { + var lastPlug = peekCommand(state); + var style = lastPlug.openBracket(ch); + source.eat(ch); + setState(state, normal); + return "bracket"; + } + if (/[ \t\r]/.test(ch)) { + source.eat(ch); + return null; + } + setState(state, normal); + lastPlug = peekCommand(state); + if (lastPlug) { + popCommand(state); + } + return normal(source, state); + } + + return { + startState: function() { return { f:normal, cmdState:[] }; }, + copyState: function(s) { return { f: s.f, cmdState: s.cmdState.slice(0, s.cmdState.length) }; }, + + token: function(stream, state) { + var t = state.f(stream, state); + var w = stream.current(); + return t; + } + }; +}); + +CodeMirror.defineMIME("text/x-stex", "stex"); +CodeMirror.defineMIME("text/x-latex", "stex"); diff --git a/app/src/main/assets/mode/tiddlywiki/tiddlywiki.css b/app/src/main/assets/mode/tiddlywiki/tiddlywiki.css new file mode 100644 index 0000000000000000000000000000000000000000..9a69b639f8a71389a99dacb03bd03e4bb2a57ded --- /dev/null +++ b/app/src/main/assets/mode/tiddlywiki/tiddlywiki.css @@ -0,0 +1,14 @@ +span.cm-underlined { + text-decoration: underline; +} +span.cm-strikethrough { + text-decoration: line-through; +} +span.cm-brace { + color: #170; + font-weight: bold; +} +span.cm-table { + color: blue; + font-weight: bold; +} diff --git a/app/src/main/assets/mode/tiddlywiki/tiddlywiki.js b/app/src/main/assets/mode/tiddlywiki/tiddlywiki.js new file mode 100644 index 0000000000000000000000000000000000000000..74fcd496646047d2bd40380e2009dcdb4cda9998 --- /dev/null +++ b/app/src/main/assets/mode/tiddlywiki/tiddlywiki.js @@ -0,0 +1,384 @@ +/*** +|''Name''|tiddlywiki.js| +|''Description''|Enables TiddlyWikiy syntax highlighting using CodeMirror| +|''Author''|PMario| +|''Version''|0.1.7| +|''Status''|''stable''| +|''Source''|[[GitHub|https://github.com/pmario/CodeMirror2/blob/tw-syntax/mode/tiddlywiki]]| +|''Documentation''|http://codemirror.tiddlyspace.com/| +|''License''|[[MIT License|http://www.opensource.org/licenses/mit-license.php]]| +|''CoreVersion''|2.5.0| +|''Requires''|codemirror.js| +|''Keywords''|syntax highlighting color code mirror codemirror| +! Info +CoreVersion parameter is needed for TiddlyWiki only! +***/ +//{{{ +CodeMirror.defineMode("tiddlywiki", function (config, parserConfig) { + var indentUnit = config.indentUnit; + + // Tokenizer + var textwords = function () { + function kw(type) { + return { + type: type, + style: "text" + }; + } + return {}; + }(); + + var keywords = function () { + function kw(type) { + return { type: type, style: "macro"}; + } + return { + "allTags": kw('allTags'), "closeAll": kw('closeAll'), "list": kw('list'), + "newJournal": kw('newJournal'), "newTiddler": kw('newTiddler'), + "permaview": kw('permaview'), "saveChanges": kw('saveChanges'), + "search": kw('search'), "slider": kw('slider'), "tabs": kw('tabs'), + "tag": kw('tag'), "tagging": kw('tagging'), "tags": kw('tags'), + "tiddler": kw('tiddler'), "timeline": kw('timeline'), + "today": kw('today'), "version": kw('version'), "option": kw('option'), + + "with": kw('with'), + "filter": kw('filter') + }; + }(); + + var isSpaceName = /[\w_\-]/i, + reHR = /^\-\-\-\-+$/, //
+ reWikiCommentStart = /^\/\*\*\*$/, // /*** + reWikiCommentStop = /^\*\*\*\/$/, // ***/ + reBlockQuote = /^<<<$/, + + reJsCodeStart = /^\/\/\{\{\{$/, // //{{{ js block start + reJsCodeStop = /^\/\/\}\}\}$/, // //}}} js stop + reXmlCodeStart = /^$/, // xml block start + reXmlCodeStop = /^$/, // xml stop + + reCodeBlockStart = /^\{\{\{$/, // {{{ TW text div block start + reCodeBlockStop = /^\}\}\}$/, // }}} TW text stop + + reCodeStart = /\{\{\{/, // {{{ code span start + reUntilCodeStop = /.*?\}\}\}/; + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + + // used for strings + function nextUntilUnescaped(stream, end) { + var escaped = false, + next; + while ((next = stream.next()) != null) { + if (next == end && !escaped) return false; + escaped = !escaped && next == "\\"; + } + return escaped; + } + + // Used as scratch variables to communicate multiple values without + // consing up tons of objects. + var type, content; + + function ret(tp, style, cont) { + type = tp; + content = cont; + return style; + } + + function jsTokenBase(stream, state) { + var sol = stream.sol(), + ch, tch; + + state.block = false; // indicates the start of a code block. + + ch = stream.peek(); // don't eat, to make matching simpler + + // check start of blocks + if (sol && /[<\/\*{}\-]/.test(ch)) { + if (stream.match(reCodeBlockStart)) { + state.block = true; + return chain(stream, state, twTokenCode); + } + if (stream.match(reBlockQuote)) { + return ret('quote', 'quote'); + } + if (stream.match(reWikiCommentStart) || stream.match(reWikiCommentStop)) { + return ret('code', 'comment'); + } + if (stream.match(reJsCodeStart) || stream.match(reJsCodeStop) || stream.match(reXmlCodeStart) || stream.match(reXmlCodeStop)) { + return ret('code', 'comment'); + } + if (stream.match(reHR)) { + return ret('hr', 'hr'); + } + } // sol + ch = stream.next(); + + if (sol && /[\/\*!#;:>|]/.test(ch)) { + if (ch == "!") { // tw header + stream.skipToEnd(); + return ret("header", "header"); + } + if (ch == "*") { // tw list + stream.eatWhile('*'); + return ret("list", "comment"); + } + if (ch == "#") { // tw numbered list + stream.eatWhile('#'); + return ret("list", "comment"); + } + if (ch == ";") { // definition list, term + stream.eatWhile(';'); + return ret("list", "comment"); + } + if (ch == ":") { // definition list, description + stream.eatWhile(':'); + return ret("list", "comment"); + } + if (ch == ">") { // single line quote + stream.eatWhile(">"); + return ret("quote", "quote"); + } + if (ch == '|') { + return ret('table', 'header'); + } + } + + if (ch == '{' && stream.match(/\{\{/)) { + return chain(stream, state, twTokenCode); + } + + // rudimentary html:// file:// link matching. TW knows much more ... + if (/[hf]/i.test(ch)) { + if (/[ti]/i.test(stream.peek()) && stream.match(/\b(ttps?|tp|ile):\/\/[\-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/i)) { + return ret("link", "link"); + } + } + // just a little string indicator, don't want to have the whole string covered + if (ch == '"') { + return ret('string', 'string'); + } + if (ch == '~') { // _no_ CamelCase indicator should be bold + return ret('text', 'brace'); + } + if (/[\[\]]/.test(ch)) { // check for [[..]] + if (stream.peek() == ch) { + stream.next(); + return ret('brace', 'brace'); + } + } + if (ch == "@") { // check for space link. TODO fix @@...@@ highlighting + stream.eatWhile(isSpaceName); + return ret("link", "link"); + } + if (/\d/.test(ch)) { // numbers + stream.eatWhile(/\d/); + return ret("number", "number"); + } + if (ch == "/") { // tw invisible comment + if (stream.eat("%")) { + return chain(stream, state, twTokenComment); + } + else if (stream.eat("/")) { // + return chain(stream, state, twTokenEm); + } + } + if (ch == "_") { // tw underline + if (stream.eat("_")) { + return chain(stream, state, twTokenUnderline); + } + } + // strikethrough and mdash handling + if (ch == "-") { + if (stream.eat("-")) { + // if strikethrough looks ugly, change CSS. + if (stream.peek() != ' ') + return chain(stream, state, twTokenStrike); + // mdash + if (stream.peek() == ' ') + return ret('text', 'brace'); + } + } + if (ch == "'") { // tw bold + if (stream.eat("'")) { + return chain(stream, state, twTokenStrong); + } + } + if (ch == "<") { // tw macro + if (stream.eat("<")) { + return chain(stream, state, twTokenMacro); + } + } + else { + return ret(ch); + } + + // core macro handling + stream.eatWhile(/[\w\$_]/); + var word = stream.current(), + known = textwords.propertyIsEnumerable(word) && textwords[word]; + + return known ? ret(known.type, known.style, word) : ret("text", null, word); + + } // jsTokenBase() + + function twTokenString(quote) { + return function (stream, state) { + if (!nextUntilUnescaped(stream, quote)) state.tokenize = jsTokenBase; + return ret("string", "string"); + }; + } + + // tw invisible comment + function twTokenComment(stream, state) { + var maybeEnd = false, + ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = jsTokenBase; + break; + } + maybeEnd = (ch == "%"); + } + return ret("comment", "comment"); + } + + // tw strong / bold + function twTokenStrong(stream, state) { + var maybeEnd = false, + ch; + while (ch = stream.next()) { + if (ch == "'" && maybeEnd) { + state.tokenize = jsTokenBase; + break; + } + maybeEnd = (ch == "'"); + } + return ret("text", "strong"); + } + + // tw code + function twTokenCode(stream, state) { + var ch, sb = state.block; + + if (sb && stream.current()) { + return ret("code", "comment"); + } + + if (!sb && stream.match(reUntilCodeStop)) { + state.tokenize = jsTokenBase; + return ret("code", "comment"); + } + + if (sb && stream.sol() && stream.match(reCodeBlockStop)) { + state.tokenize = jsTokenBase; + return ret("code", "comment"); + } + + ch = stream.next(); + return (sb) ? ret("code", "comment") : ret("code", "comment"); + } + + // tw em / italic + function twTokenEm(stream, state) { + var maybeEnd = false, + ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = jsTokenBase; + break; + } + maybeEnd = (ch == "/"); + } + return ret("text", "em"); + } + + // tw underlined text + function twTokenUnderline(stream, state) { + var maybeEnd = false, + ch; + while (ch = stream.next()) { + if (ch == "_" && maybeEnd) { + state.tokenize = jsTokenBase; + break; + } + maybeEnd = (ch == "_"); + } + return ret("text", "underlined"); + } + + // tw strike through text looks ugly + // change CSS if needed + function twTokenStrike(stream, state) { + var maybeEnd = false, + ch, nr; + + while (ch = stream.next()) { + if (ch == "-" && maybeEnd) { + state.tokenize = jsTokenBase; + break; + } + maybeEnd = (ch == "-"); + } + return ret("text", "strikethrough"); + } + + // macro + function twTokenMacro(stream, state) { + var ch, tmp, word, known; + + if (stream.current() == '<<') { + return ret('brace', 'macro'); + } + + ch = stream.next(); + if (!ch) { + state.tokenize = jsTokenBase; + return ret(ch); + } + if (ch == ">") { + if (stream.peek() == '>') { + stream.next(); + state.tokenize = jsTokenBase; + return ret("brace", "macro"); + } + } + + stream.eatWhile(/[\w\$_]/); + word = stream.current(); + known = keywords.propertyIsEnumerable(word) && keywords[word]; + + if (known) { + return ret(known.type, known.style, word); + } + else { + return ret("macro", null, word); + } + } + + // Interface + return { + startState: function (basecolumn) { + return { + tokenize: jsTokenBase, + indented: 0, + level: 0 + }; + }, + + token: function (stream, state) { + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + return style; + }, + + electricChars: "" + }; +}); + +CodeMirror.defineMIME("text/x-tiddlywiki", "tiddlywiki"); +//}}} diff --git a/app/src/main/assets/mode/tiki/tiki.css b/app/src/main/assets/mode/tiki/tiki.css new file mode 100644 index 0000000000000000000000000000000000000000..e3c3c0fd2d2bfdec704f3fcc9c4ad0903a99284a --- /dev/null +++ b/app/src/main/assets/mode/tiki/tiki.css @@ -0,0 +1,26 @@ +.cm-tw-syntaxerror { + color: #FFFFFF; + background-color: #990000; +} + +.cm-tw-deleted { + text-decoration: line-through; +} + +.cm-tw-header5 { + font-weight: bold; +} +.cm-tw-listitem:first-child { /*Added first child to fix duplicate padding when highlighting*/ + padding-left: 10px; +} + +.cm-tw-box { + border-top-width: 0px ! important; + border-style: solid; + border-width: 1px; + border-color: inherit; +} + +.cm-tw-underline { + text-decoration: underline; +} \ No newline at end of file diff --git a/app/src/main/assets/mode/tiki/tiki.js b/app/src/main/assets/mode/tiki/tiki.js new file mode 100644 index 0000000000000000000000000000000000000000..af83dc1b5b93f257027b30528eb1c8d68de492dc --- /dev/null +++ b/app/src/main/assets/mode/tiki/tiki.js @@ -0,0 +1,309 @@ +CodeMirror.defineMode('tiki', function(config, parserConfig) { + function inBlock(style, terminator, returnTokenizer) { + return function(stream, state) { + while (!stream.eol()) { + if (stream.match(terminator)) { + state.tokenize = inText; + break; + } + stream.next(); + } + + if (returnTokenizer) state.tokenize = returnTokenizer; + + return style; + }; + } + + function inLine(style, terminator) { + return function(stream, state) { + while(!stream.eol()) { + stream.next(); + } + state.tokenize = inText; + return style; + }; + } + + function inText(stream, state) { + function chain(parser) { + state.tokenize = parser; + return parser(stream, state); + } + + var sol = stream.sol(); + var ch = stream.next(); + + //non start of line + switch (ch) { //switch is generally much faster than if, so it is used here + case "{": //plugin + var type = stream.eat("/") ? "closeTag" : "openTag"; + stream.eatSpace(); + var tagName = ""; + var c; + while ((c = stream.eat(/[^\s\u00a0=\"\'\/?(}]/))) tagName += c; + state.tokenize = inPlugin; + return "tag"; + break; + case "_": //bold + if (stream.eat("_")) { + return chain(inBlock("strong", "__", inText)); + } + break; + case "'": //italics + if (stream.eat("'")) { + // Italic text + return chain(inBlock("em", "''", inText)); + } + break; + case "(":// Wiki Link + if (stream.eat("(")) { + return chain(inBlock("variable-2", "))", inText)); + } + break; + case "[":// Weblink + return chain(inBlock("variable-3", "]", inText)); + break; + case "|": //table + if (stream.eat("|")) { + return chain(inBlock("comment", "||")); + } + break; + case "-": + if (stream.eat("=")) {//titleBar + return chain(inBlock("header string", "=-", inText)); + } else if (stream.eat("-")) {//deleted + return chain(inBlock("error tw-deleted", "--", inText)); + } + break; + case "=": //underline + if (stream.match("==")) { + return chain(inBlock("tw-underline", "===", inText)); + } + break; + case ":": + if (stream.eat(":")) { + return chain(inBlock("comment", "::")); + } + break; + case "^": //box + return chain(inBlock("tw-box", "^")); + break; + case "~": //np + if (stream.match("np~")) { + return chain(inBlock("meta", "~/np~")); + } + break; + } + + //start of line types + if (sol) { + switch (ch) { + case "!": //header at start of line + if (stream.match('!!!!!')) { + return chain(inLine("header string")); + } else if (stream.match('!!!!')) { + return chain(inLine("header string")); + } else if (stream.match('!!!')) { + return chain(inLine("header string")); + } else if (stream.match('!!')) { + return chain(inLine("header string")); + } else { + return chain(inLine("header string")); + } + break; + case "*": //unordered list line item, or
  • at start of line + case "#": //ordered list line item, or
  • at start of line + case "+": //ordered list line item, or
  • at start of line + return chain(inLine("tw-listitem bracket")); + break; + } + } + + //stream.eatWhile(/[&{]/); was eating up plugins, turned off to act less like html and more like tiki + return null; + } + + var indentUnit = config.indentUnit; + + // Return variables for tokenizers + var pluginName, type; + function inPlugin(stream, state) { + var ch = stream.next(); + var peek = stream.peek(); + + if (ch == "}") { + state.tokenize = inText; + //type = ch == ")" ? "endPlugin" : "selfclosePlugin"; inPlugin + return "tag"; + } else if (ch == "(" || ch == ")") { + return "bracket"; + } else if (ch == "=") { + type = "equals"; + + if (peek == ">") { + ch = stream.next(); + peek = stream.peek(); + } + + //here we detect values directly after equal character with no quotes + if (!/[\'\"]/.test(peek)) { + state.tokenize = inAttributeNoQuote(); + } + //end detect values + + return "operator"; + } else if (/[\'\"]/.test(ch)) { + state.tokenize = inAttribute(ch); + return state.tokenize(stream, state); + } else { + stream.eatWhile(/[^\s\u00a0=\"\'\/?]/); + return "keyword"; + } + } + + function inAttribute(quote) { + return function(stream, state) { + while (!stream.eol()) { + if (stream.next() == quote) { + state.tokenize = inPlugin; + break; + } + } + return "string"; + }; + } + + function inAttributeNoQuote() { + return function(stream, state) { + while (!stream.eol()) { + var ch = stream.next(); + var peek = stream.peek(); + if (ch == " " || ch == "," || /[ )}]/.test(peek)) { + state.tokenize = inPlugin; + break; + } + } + return "string"; + }; + } + + var curState, setStyle; + function pass() { + for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]); + } + + function cont() { + pass.apply(null, arguments); + return true; + } + + function pushContext(pluginName, startOfLine) { + var noIndent = curState.context && curState.context.noIndent; + curState.context = { + prev: curState.context, + pluginName: pluginName, + indent: curState.indented, + startOfLine: startOfLine, + noIndent: noIndent + }; + } + + function popContext() { + if (curState.context) curState.context = curState.context.prev; + } + + function element(type) { + if (type == "openPlugin") {curState.pluginName = pluginName; return cont(attributes, endplugin(curState.startOfLine));} + else if (type == "closePlugin") { + var err = false; + if (curState.context) { + err = curState.context.pluginName != pluginName; + popContext(); + } else { + err = true; + } + if (err) setStyle = "error"; + return cont(endcloseplugin(err)); + } + else if (type == "string") { + if (!curState.context || curState.context.name != "!cdata") pushContext("!cdata"); + if (curState.tokenize == inText) popContext(); + return cont(); + } + else return cont(); + } + + function endplugin(startOfLine) { + return function(type) { + if ( + type == "selfclosePlugin" || + type == "endPlugin" + ) + return cont(); + if (type == "endPlugin") {pushContext(curState.pluginName, startOfLine); return cont();} + return cont(); + }; + } + + function endcloseplugin(err) { + return function(type) { + if (err) setStyle = "error"; + if (type == "endPlugin") return cont(); + return pass(); + }; + } + + function attributes(type) { + if (type == "keyword") {setStyle = "attribute"; return cont(attributes);} + if (type == "equals") return cont(attvalue, attributes); + return pass(); + } + function attvalue(type) { + if (type == "keyword") {setStyle = "string"; return cont();} + if (type == "string") return cont(attvaluemaybe); + return pass(); + } + function attvaluemaybe(type) { + if (type == "string") return cont(attvaluemaybe); + else return pass(); + } + return { + startState: function() { + return {tokenize: inText, cc: [], indented: 0, startOfLine: true, pluginName: null, context: null}; + }, + token: function(stream, state) { + if (stream.sol()) { + state.startOfLine = true; + state.indented = stream.indentation(); + } + if (stream.eatSpace()) return null; + + setStyle = type = pluginName = null; + var style = state.tokenize(stream, state); + if ((style || type) && style != "comment") { + curState = state; + while (true) { + var comb = state.cc.pop() || element; + if (comb(type || style)) break; + } + } + state.startOfLine = false; + return setStyle || style; + }, + indent: function(state, textAfter) { + var context = state.context; + if (context && context.noIndent) return 0; + if (context && /^{\//.test(textAfter)) + context = context.prev; + while (context && !context.startOfLine) + context = context.prev; + if (context) return context.indent + indentUnit; + else return 0; + }, + electricChars: "/" + }; +}); + +//I figure, why not +CodeMirror.defineMIME("text/tiki", "tiki"); diff --git a/app/src/main/assets/mode/vb/LICENSE.txt b/app/src/main/assets/mode/vb/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..60839703a97eb623070a57da751df1dfc903327e --- /dev/null +++ b/app/src/main/assets/mode/vb/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2012 Codility Limited, 107 Cheapside, London EC2V 6DN, UK + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/app/src/main/assets/mode/vb/vb.js b/app/src/main/assets/mode/vb/vb.js new file mode 100644 index 0000000000000000000000000000000000000000..be01d13ad99a6ad69a2192f245d02a470b6148d8 --- /dev/null +++ b/app/src/main/assets/mode/vb/vb.js @@ -0,0 +1,260 @@ +CodeMirror.defineMode("vb", function(conf, parserConf) { + var ERRORCLASS = 'error'; + + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b", "i"); + } + + var singleOperators = new RegExp("^[\\+\\-\\*/%&\\\\|\\^~<>!]"); + var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]'); + var doubleOperators = new RegExp("^((==)|(<>)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))"); + var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); + var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))"); + var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*"); + + var openingKeywords = ['class','module', 'sub','enum','select','while','if','function', 'get','set','property', 'try']; + var middleKeywords = ['else','elseif','case', 'catch']; + var endKeywords = ['next','loop']; + + var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'in']); + var commonkeywords = ['as', 'dim', 'break', 'continue','optional', 'then', 'until', + 'goto', 'byval','byref','new','handles','property', 'return', + 'const','private', 'protected', 'friend', 'public', 'shared', 'static', 'true','false']; + var commontypes = ['integer','string','double','decimal','boolean','short','char', 'float','single']; + + var keywords = wordRegexp(commonkeywords); + var types = wordRegexp(commontypes); + var stringPrefixes = '"'; + + var opening = wordRegexp(openingKeywords); + var middle = wordRegexp(middleKeywords); + var closing = wordRegexp(endKeywords); + var doubleClosing = wordRegexp(['end']); + var doOpening = wordRegexp(['do']); + + var indentInfo = null; + + + + + function indent(stream, state) { + state.currentIndent++; + } + + function dedent(stream, state) { + state.currentIndent--; + } + // tokenizers + function tokenBase(stream, state) { + if (stream.eatSpace()) { + return null; + } + + var ch = stream.peek(); + + // Handle Comments + if (ch === "'") { + stream.skipToEnd(); + return 'comment'; + } + + + // Handle Number Literals + if (stream.match(/^((&H)|(&O))?[0-9\.a-f]/i, false)) { + var floatLiteral = false; + // Floats + if (stream.match(/^\d*\.\d+F?/i)) { floatLiteral = true; } + else if (stream.match(/^\d+\.\d*F?/)) { floatLiteral = true; } + else if (stream.match(/^\.\d+F?/)) { floatLiteral = true; } + + if (floatLiteral) { + // Float literals may be "imaginary" + stream.eat(/J/i); + return 'number'; + } + // Integers + var intLiteral = false; + // Hex + if (stream.match(/^&H[0-9a-f]+/i)) { intLiteral = true; } + // Octal + else if (stream.match(/^&O[0-7]+/i)) { intLiteral = true; } + // Decimal + else if (stream.match(/^[1-9]\d*F?/)) { + // Decimal literals may be "imaginary" + stream.eat(/J/i); + // TODO - Can you have imaginary longs? + intLiteral = true; + } + // Zero by itself with no other piece of number. + else if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; } + if (intLiteral) { + // Integer literals may be "long" + stream.eat(/L/i); + return 'number'; + } + } + + // Handle Strings + if (stream.match(stringPrefixes)) { + state.tokenize = tokenStringFactory(stream.current()); + return state.tokenize(stream, state); + } + + // Handle operators and Delimiters + if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { + return null; + } + if (stream.match(doubleOperators) + || stream.match(singleOperators) + || stream.match(wordOperators)) { + return 'operator'; + } + if (stream.match(singleDelimiters)) { + return null; + } + if (stream.match(doOpening)) { + indent(stream,state); + state.doInCurrentLine = true; + return 'keyword'; + } + if (stream.match(opening)) { + if (! state.doInCurrentLine) + indent(stream,state); + else + state.doInCurrentLine = false; + return 'keyword'; + } + if (stream.match(middle)) { + return 'keyword'; + } + + if (stream.match(doubleClosing)) { + dedent(stream,state); + dedent(stream,state); + return 'keyword'; + } + if (stream.match(closing)) { + dedent(stream,state); + return 'keyword'; + } + + if (stream.match(types)) { + return 'keyword'; + } + + if (stream.match(keywords)) { + return 'keyword'; + } + + if (stream.match(identifiers)) { + return 'variable'; + } + + // Handle non-detected items + stream.next(); + return ERRORCLASS; + } + + function tokenStringFactory(delimiter) { + var singleline = delimiter.length == 1; + var OUTCLASS = 'string'; + + return function tokenString(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^'"]/); + if (stream.match(delimiter)) { + state.tokenize = tokenBase; + return OUTCLASS; + } else { + stream.eat(/['"]/); + } + } + if (singleline) { + if (parserConf.singleLineStringErrors) { + return ERRORCLASS; + } else { + state.tokenize = tokenBase; + } + } + return OUTCLASS; + }; + } + + + function tokenLexer(stream, state) { + var style = state.tokenize(stream, state); + var current = stream.current(); + + // Handle '.' connected identifiers + if (current === '.') { + style = state.tokenize(stream, state); + current = stream.current(); + if (style === 'variable') { + return 'variable'; + } else { + return ERRORCLASS; + } + } + + + var delimiter_index = '[({'.indexOf(current); + if (delimiter_index !== -1) { + indent(stream, state ); + } + if (indentInfo === 'dedent') { + if (dedent(stream, state)) { + return ERRORCLASS; + } + } + delimiter_index = '])}'.indexOf(current); + if (delimiter_index !== -1) { + if (dedent(stream, state)) { + return ERRORCLASS; + } + } + + return style; + } + + var external = { + electricChars:"dDpPtTfFeE ", + startState: function(basecolumn) { + return { + tokenize: tokenBase, + lastToken: null, + currentIndent: 0, + nextLineIndent: 0, + doInCurrentLine: false + + + }; + }, + + token: function(stream, state) { + if (stream.sol()) { + state.currentIndent += state.nextLineIndent; + state.nextLineIndent = 0; + state.doInCurrentLine = 0; + } + var style = tokenLexer(stream, state); + + state.lastToken = {style:style, content: stream.current()}; + + + + return style; + }, + + indent: function(state, textAfter) { + var trueText = textAfter.replace(/^\s+|\s+$/g, '') ; + if (trueText.match(closing) || trueText.match(doubleClosing) || trueText.match(middle)) return conf.indentUnit*(state.currentIndent-1); + if(state.currentIndent < 0) return 0; + return state.currentIndent * conf.indentUnit; + } + + }; + return external; +}); + +CodeMirror.defineMIME("text/x-vb", "vb"); + diff --git a/app/src/main/assets/mode/vbscript/vbscript.js b/app/src/main/assets/mode/vbscript/vbscript.js new file mode 100644 index 0000000000000000000000000000000000000000..65d6c212767a64d4dc34df33d3185d56fc6ffc8b --- /dev/null +++ b/app/src/main/assets/mode/vbscript/vbscript.js @@ -0,0 +1,26 @@ +CodeMirror.defineMode("vbscript", function() { + var regexVBScriptKeyword = /^(?:Call|Case|CDate|Clear|CInt|CLng|Const|CStr|Description|Dim|Do|Each|Else|ElseIf|End|Err|Error|Exit|False|For|Function|If|LCase|Loop|LTrim|Next|Nothing|Now|Number|On|Preserve|Quit|ReDim|Resume|RTrim|Select|Set|Sub|Then|To|Trim|True|UBound|UCase|Until|VbCr|VbCrLf|VbLf|VbTab)$/im; + + return { + token: function(stream) { + if (stream.eatSpace()) return null; + var ch = stream.next(); + if (ch == "'") { + stream.skipToEnd(); + return "comment"; + } + if (ch == '"') { + stream.skipTo('"'); + return "string"; + } + + if (/\w/.test(ch)) { + stream.eatWhile(/\w/); + if (regexVBScriptKeyword.test(stream.current())) return "keyword"; + } + return null; + } + }; +}); + +CodeMirror.defineMIME("text/vbscript", "vbscript"); diff --git a/app/src/main/assets/mode/velocity/velocity.js b/app/src/main/assets/mode/velocity/velocity.js new file mode 100644 index 0000000000000000000000000000000000000000..b7048b152f1466bee6c44987def75dbbe1ac03a9 --- /dev/null +++ b/app/src/main/assets/mode/velocity/velocity.js @@ -0,0 +1,146 @@ +CodeMirror.defineMode("velocity", function(config) { + function parseWords(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var indentUnit = config.indentUnit; + var keywords = parseWords("#end #else #break #stop #[[ #]] " + + "#{end} #{else} #{break} #{stop}"); + var functions = parseWords("#if #elseif #foreach #set #include #parse #macro #define #evaluate " + + "#{if} #{elseif} #{foreach} #{set} #{include} #{parse} #{macro} #{define} #{evaluate}"); + var specials = parseWords("$foreach.count $foreach.hasNext $foreach.first $foreach.last $foreach.topmost $foreach.parent $velocityCount"); + var isOperatorChar = /[+\-*&%=<>!?:\/|]/; + var multiLineStrings =true; + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + function tokenBase(stream, state) { + var beforeParams = state.beforeParams; + state.beforeParams = false; + var ch = stream.next(); + // start of string? + if ((ch == '"' || ch == "'") && state.inParams) + return chain(stream, state, tokenString(ch)); + // is it one of the special signs []{}().,;? Seperator? + else if (/[\[\]{}\(\),;\.]/.test(ch)) { + if (ch == "(" && beforeParams) state.inParams = true; + else if (ch == ")") state.inParams = false; + return null; + } + // start of a number value? + else if (/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + return "number"; + } + // multi line comment? + else if (ch == "#" && stream.eat("*")) { + return chain(stream, state, tokenComment); + } + // unparsed content? + else if (ch == "#" && stream.match(/ *\[ *\[/)) { + return chain(stream, state, tokenUnparsed); + } + // single line comment? + else if (ch == "#" && stream.eat("#")) { + stream.skipToEnd(); + return "comment"; + } + // variable? + else if (ch == "$") { + stream.eatWhile(/[\w\d\$_\.{}]/); + // is it one of the specials? + if (specials && specials.propertyIsEnumerable(stream.current().toLowerCase())) { + return "keyword"; + } + else { + state.beforeParams = true; + return "builtin"; + } + } + // is it a operator? + else if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return "operator"; + } + else { + // get the whole word + stream.eatWhile(/[\w\$_{}]/); + var word = stream.current().toLowerCase(); + // is it one of the listed keywords? + if (keywords && keywords.propertyIsEnumerable(word)) + return "keyword"; + // is it one of the listed functions? + if (functions && functions.propertyIsEnumerable(word) || + stream.current().match(/^#[a-z0-9_]+ *$/i) && stream.peek()=="(") { + state.beforeParams = true; + return "keyword"; + } + // default: just a "word" + return null; + } + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) { + end = true; + break; + } + escaped = !escaped && next == "\\"; + } + if (end) state.tokenize = tokenBase; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "#" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function tokenUnparsed(stream, state) { + var maybeEnd = 0, ch; + while (ch = stream.next()) { + if (ch == "#" && maybeEnd == 2) { + state.tokenize = tokenBase; + break; + } + if (ch == "]") + maybeEnd++; + else if (ch != " ") + maybeEnd = 0; + } + return "meta"; + } + // Interface + + return { + startState: function(basecolumn) { + return { + tokenize: tokenBase, + beforeParams: false, + inParams: false + }; + }, + + token: function(stream, state) { + if (stream.eatSpace()) return null; + return state.tokenize(stream, state); + } + }; +}); + +CodeMirror.defineMIME("text/velocity", "velocity"); diff --git a/app/src/main/assets/mode/verilog/verilog.js b/app/src/main/assets/mode/verilog/verilog.js new file mode 100644 index 0000000000000000000000000000000000000000..51fd5800eaae00a87599314ae23d83105d42d06d --- /dev/null +++ b/app/src/main/assets/mode/verilog/verilog.js @@ -0,0 +1,194 @@ +CodeMirror.defineMode("verilog", function(config, parserConfig) { + var indentUnit = config.indentUnit, + keywords = parserConfig.keywords || {}, + blockKeywords = parserConfig.blockKeywords || {}, + atoms = parserConfig.atoms || {}, + hooks = parserConfig.hooks || {}, + multiLineStrings = parserConfig.multiLineStrings; + var isOperatorChar = /[&|~>")); + else return null; + } + else if (stream.match("--")) return chain(inBlock("comment", "-->")); + else if (stream.match("DOCTYPE", true, true)) { + stream.eatWhile(/[\w\._\-]/); + return chain(doctype(1)); + } + else return null; + } + else if (stream.eat("?")) { + stream.eatWhile(/[\w\._\-]/); + state.tokenize = inBlock("meta", "?>"); + return "meta"; + } + else { + type = stream.eat("/") ? "closeTag" : "openTag"; + stream.eatSpace(); + tagName = ""; + var c; + while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; + state.tokenize = inTag; + return "tag"; + } + } + else if (ch == "&") { + var ok; + if (stream.eat("#")) { + if (stream.eat("x")) { + ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); + } else { + ok = stream.eatWhile(/[\d]/) && stream.eat(";"); + } + } else { + ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); + } + return ok ? "atom" : "error"; + } + else { + stream.eatWhile(/[^&<]/); + return null; + } + } + + function inTag(stream, state) { + var ch = stream.next(); + if (ch == ">" || (ch == "/" && stream.eat(">"))) { + state.tokenize = inText; + type = ch == ">" ? "endTag" : "selfcloseTag"; + return "tag"; + } + else if (ch == "=") { + type = "equals"; + return null; + } + else if (/[\'\"]/.test(ch)) { + state.tokenize = inAttribute(ch); + return state.tokenize(stream, state); + } + else { + stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/); + return "word"; + } + } + + function inAttribute(quote) { + return function(stream, state) { + while (!stream.eol()) { + if (stream.next() == quote) { + state.tokenize = inTag; + break; + } + } + return "string"; + }; + } + + function inBlock(style, terminator) { + return function(stream, state) { + while (!stream.eol()) { + if (stream.match(terminator)) { + state.tokenize = inText; + break; + } + stream.next(); + } + return style; + }; + } + function doctype(depth) { + return function(stream, state) { + var ch; + while ((ch = stream.next()) != null) { + if (ch == "<") { + state.tokenize = doctype(depth + 1); + return state.tokenize(stream, state); + } else if (ch == ">") { + if (depth == 1) { + state.tokenize = inText; + break; + } else { + state.tokenize = doctype(depth - 1); + return state.tokenize(stream, state); + } + } + } + return "meta"; + }; + } + + var curState, setStyle; + function pass() { + for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]); + } + function cont() { + pass.apply(null, arguments); + return true; + } + + function pushContext(tagName, startOfLine) { + var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent); + curState.context = { + prev: curState.context, + tagName: tagName, + indent: curState.indented, + startOfLine: startOfLine, + noIndent: noIndent + }; + } + function popContext() { + if (curState.context) curState.context = curState.context.prev; + } + + function element(type) { + if (type == "openTag") { + curState.tagName = tagName; + return cont(attributes, endtag(curState.startOfLine)); + } else if (type == "closeTag") { + var err = false; + if (curState.context) { + if (curState.context.tagName != tagName) { + if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) { + popContext(); + } + err = !curState.context || curState.context.tagName != tagName; + } + } else { + err = true; + } + if (err) setStyle = "error"; + return cont(endclosetag(err)); + } + return cont(); + } + function endtag(startOfLine) { + return function(type) { + if (type == "selfcloseTag" || + (type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase()))) { + maybePopContext(curState.tagName.toLowerCase()); + return cont(); + } + if (type == "endTag") { + maybePopContext(curState.tagName.toLowerCase()); + pushContext(curState.tagName, startOfLine); + return cont(); + } + return cont(); + }; + } + function endclosetag(err) { + return function(type) { + if (err) setStyle = "error"; + if (type == "endTag") { popContext(); return cont(); } + setStyle = "error"; + return cont(arguments.callee); + }; + } + function maybePopContext(nextTagName) { + var parentTagName; + while (true) { + if (!curState.context) { + return; + } + parentTagName = curState.context.tagName.toLowerCase(); + if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) || + !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { + return; + } + popContext(); + } + } + + function attributes(type) { + if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);} + if (type == "endTag" || type == "selfcloseTag") return pass(); + setStyle = "error"; + return cont(attributes); + } + function attribute(type) { + if (type == "equals") return cont(attvalue, attributes); + if (!Kludges.allowMissing) setStyle = "error"; + return (type == "endTag" || type == "selfcloseTag") ? pass() : cont(); + } + function attvalue(type) { + if (type == "string") return cont(attvaluemaybe); + if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();} + setStyle = "error"; + return (type == "endTag" || type == "selfCloseTag") ? pass() : cont(); + } + function attvaluemaybe(type) { + if (type == "string") return cont(attvaluemaybe); + else return pass(); + } + + return { + startState: function() { + return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null}; + }, + + token: function(stream, state) { + if (stream.sol()) { + state.startOfLine = true; + state.indented = stream.indentation(); + } + if (stream.eatSpace()) return null; + + setStyle = type = tagName = null; + var style = state.tokenize(stream, state); + state.type = type; + if ((style || type) && style != "comment") { + curState = state; + while (true) { + var comb = state.cc.pop() || element; + if (comb(type || style)) break; + } + } + state.startOfLine = false; + return setStyle || style; + }, + + indent: function(state, textAfter, fullLine) { + var context = state.context; + if ((state.tokenize != inTag && state.tokenize != inText) || + context && context.noIndent) + return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; + if (alignCDATA && / + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/app/src/main/assets/mode/xquery/xquery.js b/app/src/main/assets/mode/xquery/xquery.js new file mode 100644 index 0000000000000000000000000000000000000000..dfb6d7e0683769bf06f398423042a97b811012d0 --- /dev/null +++ b/app/src/main/assets/mode/xquery/xquery.js @@ -0,0 +1,451 @@ +/* +Copyright (C) 2011 by MarkLogic Corporation +Author: Mike Brevoort + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +CodeMirror.defineMode("xquery", function(config, parserConfig) { + + // The keywords object is set to the result of this self executing + // function. Each keyword is a property of the keywords object whose + // value is {type: atype, style: astyle} + var keywords = function(){ + // conveinence functions used to build keywords object + function kw(type) {return {type: type, style: "keyword"};} + var A = kw("keyword a") + , B = kw("keyword b") + , C = kw("keyword c") + , operator = kw("operator") + , atom = {type: "atom", style: "atom"} + , punctuation = {type: "punctuation", style: ""} + , qualifier = {type: "axis_specifier", style: "qualifier"}; + + // kwObj is what is return from this function at the end + var kwObj = { + 'if': A, 'switch': A, 'while': A, 'for': A, + 'else': B, 'then': B, 'try': B, 'finally': B, 'catch': B, + 'element': C, 'attribute': C, 'let': C, 'implements': C, 'import': C, 'module': C, 'namespace': C, + 'return': C, 'super': C, 'this': C, 'throws': C, 'where': C, 'private': C, + ',': punctuation, + 'null': atom, 'fn:false()': atom, 'fn:true()': atom + }; + + // a list of 'basic' keywords. For each add a property to kwObj with the value of + // {type: basic[i], style: "keyword"} e.g. 'after' --> {type: "after", style: "keyword"} + var basic = ['after','ancestor','ancestor-or-self','and','as','ascending','assert','attribute','before', + 'by','case','cast','child','comment','declare','default','define','descendant','descendant-or-self', + 'descending','document','document-node','element','else','eq','every','except','external','following', + 'following-sibling','follows','for','function','if','import','in','instance','intersect','item', + 'let','module','namespace','node','node','of','only','or','order','parent','precedes','preceding', + 'preceding-sibling','processing-instruction','ref','return','returns','satisfies','schema','schema-element', + 'self','some','sortby','stable','text','then','to','treat','typeswitch','union','variable','version','where', + 'xquery', 'empty-sequence']; + for(var i=0, l=basic.length; i < l; i++) { kwObj[basic[i]] = kw(basic[i]);}; + + // a list of types. For each add a property to kwObj with the value of + // {type: "atom", style: "atom"} + var types = ['xs:string', 'xs:float', 'xs:decimal', 'xs:double', 'xs:integer', 'xs:boolean', 'xs:date', 'xs:dateTime', + 'xs:time', 'xs:duration', 'xs:dayTimeDuration', 'xs:time', 'xs:yearMonthDuration', 'numeric', 'xs:hexBinary', + 'xs:base64Binary', 'xs:anyURI', 'xs:QName', 'xs:byte','xs:boolean','xs:anyURI','xf:yearMonthDuration']; + for(var i=0, l=types.length; i < l; i++) { kwObj[types[i]] = atom;}; + + // each operator will add a property to kwObj with value of {type: "operator", style: "keyword"} + var operators = ['eq', 'ne', 'lt', 'le', 'gt', 'ge', ':=', '=', '>', '>=', '<', '<=', '.', '|', '?', 'and', 'or', 'div', 'idiv', 'mod', '*', '/', '+', '-']; + for(var i=0, l=operators.length; i < l; i++) { kwObj[operators[i]] = operator;}; + + // each axis_specifiers will add a property to kwObj with value of {type: "axis_specifier", style: "qualifier"} + var axis_specifiers = ["self::", "attribute::", "child::", "descendant::", "descendant-or-self::", "parent::", + "ancestor::", "ancestor-or-self::", "following::", "preceding::", "following-sibling::", "preceding-sibling::"]; + for(var i=0, l=axis_specifiers.length; i < l; i++) { kwObj[axis_specifiers[i]] = qualifier; }; + + return kwObj; + }(); + + // Used as scratch variables to communicate multiple values without + // consing up tons of objects. + var type, content; + + function ret(tp, style, cont) { + type = tp; content = cont; + return style; + } + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + + // the primary mode tokenizer + function tokenBase(stream, state) { + var ch = stream.next(), + mightBeFunction = false, + isEQName = isEQNameAhead(stream); + + // an XML tag (if not in some sub, chained tokenizer) + if (ch == "<") { + if(stream.match("!--", true)) + return chain(stream, state, tokenXMLComment); + + if(stream.match("![CDATA", false)) { + state.tokenize = tokenCDATA; + return ret("tag", "tag"); + } + + if(stream.match("?", false)) { + return chain(stream, state, tokenPreProcessing); + } + + var isclose = stream.eat("/"); + stream.eatSpace(); + var tagName = "", c; + while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; + + return chain(stream, state, tokenTag(tagName, isclose)); + } + // start code block + else if(ch == "{") { + pushStateStack(state,{ type: "codeblock"}); + return ret("", ""); + } + // end code block + else if(ch == "}") { + popStateStack(state); + return ret("", ""); + } + // if we're in an XML block + else if(isInXmlBlock(state)) { + if(ch == ">") + return ret("tag", "tag"); + else if(ch == "/" && stream.eat(">")) { + popStateStack(state); + return ret("tag", "tag"); + } + else + return ret("word", "variable"); + } + // if a number + else if (/\d/.test(ch)) { + stream.match(/^\d*(?:\.\d*)?(?:E[+\-]?\d+)?/); + return ret("number", "atom"); + } + // comment start + else if (ch === "(" && stream.eat(":")) { + pushStateStack(state, { type: "comment"}); + return chain(stream, state, tokenComment); + } + // quoted string + else if ( !isEQName && (ch === '"' || ch === "'")) + return chain(stream, state, tokenString(ch)); + // variable + else if(ch === "$") { + return chain(stream, state, tokenVariable); + } + // assignment + else if(ch ===":" && stream.eat("=")) { + return ret("operator", "keyword"); + } + // open paren + else if(ch === "(") { + pushStateStack(state, { type: "paren"}); + return ret("", ""); + } + // close paren + else if(ch === ")") { + popStateStack(state); + return ret("", ""); + } + // open paren + else if(ch === "[") { + pushStateStack(state, { type: "bracket"}); + return ret("", ""); + } + // close paren + else if(ch === "]") { + popStateStack(state); + return ret("", ""); + } + else { + var known = keywords.propertyIsEnumerable(ch) && keywords[ch]; + + // if there's a EQName ahead, consume the rest of the string portion, it's likely a function + if(isEQName && ch === '\"') while(stream.next() !== '"'){} + if(isEQName && ch === '\'') while(stream.next() !== '\''){} + + // gobble up a word if the character is not known + if(!known) stream.eatWhile(/[\w\$_-]/); + + // gobble a colon in the case that is a lib func type call fn:doc + var foundColon = stream.eat(":"); + + // if there's not a second colon, gobble another word. Otherwise, it's probably an axis specifier + // which should get matched as a keyword + if(!stream.eat(":") && foundColon) { + stream.eatWhile(/[\w\$_-]/); + } + // if the next non whitespace character is an open paren, this is probably a function (if not a keyword of other sort) + if(stream.match(/^[ \t]*\(/, false)) { + mightBeFunction = true; + } + // is the word a keyword? + var word = stream.current(); + known = keywords.propertyIsEnumerable(word) && keywords[word]; + + // if we think it's a function call but not yet known, + // set style to variable for now for lack of something better + if(mightBeFunction && !known) known = {type: "function_call", style: "variable def"}; + + // if the previous word was element, attribute, axis specifier, this word should be the name of that + if(isInXmlConstructor(state)) { + popStateStack(state); + return ret("word", "variable", word); + } + // as previously checked, if the word is element,attribute, axis specifier, call it an "xmlconstructor" and + // push the stack so we know to look for it on the next word + if(word == "element" || word == "attribute" || known.type == "axis_specifier") pushStateStack(state, {type: "xmlconstructor"}); + + // if the word is known, return the details of that else just call this a generic 'word' + return known ? ret(known.type, known.style, word) : + ret("word", "variable", word); + } + } + + // handle comments, including nested + function tokenComment(stream, state) { + var maybeEnd = false, maybeNested = false, nestedCount = 0, ch; + while (ch = stream.next()) { + if (ch == ")" && maybeEnd) { + if(nestedCount > 0) + nestedCount--; + else { + popStateStack(state); + break; + } + } + else if(ch == ":" && maybeNested) { + nestedCount++; + } + maybeEnd = (ch == ":"); + maybeNested = (ch == "("); + } + + return ret("comment", "comment"); + } + + // tokenizer for string literals + // optionally pass a tokenizer function to set state.tokenize back to when finished + function tokenString(quote, f) { + return function(stream, state) { + var ch; + + if(isInString(state) && stream.current() == quote) { + popStateStack(state); + if(f) state.tokenize = f; + return ret("string", "string"); + } + + pushStateStack(state, { type: "string", name: quote, tokenize: tokenString(quote, f) }); + + // if we're in a string and in an XML block, allow an embedded code block + if(stream.match("{", false) && isInXmlAttributeBlock(state)) { + state.tokenize = tokenBase; + return ret("string", "string"); + } + + + while (ch = stream.next()) { + if (ch == quote) { + popStateStack(state); + if(f) state.tokenize = f; + break; + } + else { + // if we're in a string and in an XML block, allow an embedded code block in an attribute + if(stream.match("{", false) && isInXmlAttributeBlock(state)) { + state.tokenize = tokenBase; + return ret("string", "string"); + } + + } + } + + return ret("string", "string"); + }; + } + + // tokenizer for variables + function tokenVariable(stream, state) { + var isVariableChar = /[\w\$_-]/; + + // a variable may start with a quoted EQName so if the next character is quote, consume to the next quote + if(stream.eat("\"")) { + while(stream.next() !== '\"'){}; + stream.eat(":"); + } else { + stream.eatWhile(isVariableChar); + if(!stream.match(":=", false)) stream.eat(":"); + } + stream.eatWhile(isVariableChar); + state.tokenize = tokenBase; + return ret("variable", "variable"); + } + + // tokenizer for XML tags + function tokenTag(name, isclose) { + return function(stream, state) { + stream.eatSpace(); + if(isclose && stream.eat(">")) { + popStateStack(state); + state.tokenize = tokenBase; + return ret("tag", "tag"); + } + // self closing tag without attributes? + if(!stream.eat("/")) + pushStateStack(state, { type: "tag", name: name, tokenize: tokenBase}); + if(!stream.eat(">")) { + state.tokenize = tokenAttribute; + return ret("tag", "tag"); + } + else { + state.tokenize = tokenBase; + } + return ret("tag", "tag"); + }; + } + + // tokenizer for XML attributes + function tokenAttribute(stream, state) { + var ch = stream.next(); + + if(ch == "/" && stream.eat(">")) { + if(isInXmlAttributeBlock(state)) popStateStack(state); + if(isInXmlBlock(state)) popStateStack(state); + return ret("tag", "tag"); + } + if(ch == ">") { + if(isInXmlAttributeBlock(state)) popStateStack(state); + return ret("tag", "tag"); + } + if(ch == "=") + return ret("", ""); + // quoted string + if (ch == '"' || ch == "'") + return chain(stream, state, tokenString(ch, tokenAttribute)); + + if(!isInXmlAttributeBlock(state)) + pushStateStack(state, { type: "attribute", name: name, tokenize: tokenAttribute}); + + stream.eat(/[a-zA-Z_:]/); + stream.eatWhile(/[-a-zA-Z0-9_:.]/); + stream.eatSpace(); + + // the case where the attribute has not value and the tag was closed + if(stream.match(">", false) || stream.match("/", false)) { + popStateStack(state); + state.tokenize = tokenBase; + } + + return ret("attribute", "attribute"); + } + + // handle comments, including nested + function tokenXMLComment(stream, state) { + var ch; + while (ch = stream.next()) { + if (ch == "-" && stream.match("->", true)) { + state.tokenize = tokenBase; + return ret("comment", "comment"); + } + } + } + + + // handle CDATA + function tokenCDATA(stream, state) { + var ch; + while (ch = stream.next()) { + if (ch == "]" && stream.match("]", true)) { + state.tokenize = tokenBase; + return ret("comment", "comment"); + } + } + } + + // handle preprocessing instructions + function tokenPreProcessing(stream, state) { + var ch; + while (ch = stream.next()) { + if (ch == "?" && stream.match(">", true)) { + state.tokenize = tokenBase; + return ret("comment", "comment meta"); + } + } + } + + + // functions to test the current context of the state + function isInXmlBlock(state) { return isIn(state, "tag"); } + function isInXmlAttributeBlock(state) { return isIn(state, "attribute"); } + function isInCodeBlock(state) { return isIn(state, "codeblock"); } + function isInXmlConstructor(state) { return isIn(state, "xmlconstructor"); } + function isInString(state) { return isIn(state, "string"); } + + function isEQNameAhead(stream) { + // assume we've already eaten a quote (") + if(stream.current() === '"') + return stream.match(/^[^\"]+\"\:/, false); + else if(stream.current() === '\'') + return stream.match(/^[^\"]+\'\:/, false); + else + return false; + } + + function isIn(state, type) { + return (state.stack.length && state.stack[state.stack.length - 1].type == type); + } + + function pushStateStack(state, newState) { + state.stack.push(newState); + } + + function popStateStack(state) { + var popped = state.stack.pop(); + var reinstateTokenize = state.stack.length && state.stack[state.stack.length-1].tokenize; + state.tokenize = reinstateTokenize || tokenBase; + } + + // the interface for the mode API + return { + startState: function(basecolumn) { + return { + tokenize: tokenBase, + cc: [], + stack: [] + }; + }, + + token: function(stream, state) { + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + return style; + } + }; + +}); + +CodeMirror.defineMIME("application/xquery", "xquery"); diff --git a/app/src/main/assets/mode/yaml/yaml.js b/app/src/main/assets/mode/yaml/yaml.js new file mode 100644 index 0000000000000000000000000000000000000000..59e2641a016eedc79ee7c43259ee1f6923c384ff --- /dev/null +++ b/app/src/main/assets/mode/yaml/yaml.js @@ -0,0 +1,95 @@ +CodeMirror.defineMode("yaml", function() { + + var cons = ['true', 'false', 'on', 'off', 'yes', 'no']; + var keywordRegex = new RegExp("\\b(("+cons.join(")|(")+"))$", 'i'); + + return { + token: function(stream, state) { + var ch = stream.peek(); + var esc = state.escaped; + state.escaped = false; + /* comments */ + if (ch == "#") { stream.skipToEnd(); return "comment"; } + if (state.literal && stream.indentation() > state.keyCol) { + stream.skipToEnd(); return "string"; + } else if (state.literal) { state.literal = false; } + if (stream.sol()) { + state.keyCol = 0; + state.pair = false; + state.pairStart = false; + /* document start */ + if(stream.match(/---/)) { return "def"; } + /* document end */ + if (stream.match(/\.\.\./)) { return "def"; } + /* array list item */ + if (stream.match(/\s*-\s+/)) { return 'meta'; } + } + /* pairs (associative arrays) -> key */ + if (!state.pair && stream.match(/^\s*([a-z0-9\._-])+(?=\s*:)/i)) { + state.pair = true; + state.keyCol = stream.indentation(); + return "atom"; + } + if (state.pair && stream.match(/^:\s*/)) { state.pairStart = true; return 'meta'; } + + /* inline pairs/lists */ + if (stream.match(/^(\{|\}|\[|\])/)) { + if (ch == '{') + state.inlinePairs++; + else if (ch == '}') + state.inlinePairs--; + else if (ch == '[') + state.inlineList++; + else + state.inlineList--; + return 'meta'; + } + + /* list seperator */ + if (state.inlineList > 0 && !esc && ch == ',') { + stream.next(); + return 'meta'; + } + /* pairs seperator */ + if (state.inlinePairs > 0 && !esc && ch == ',') { + state.keyCol = 0; + state.pair = false; + state.pairStart = false; + stream.next(); + return 'meta'; + } + + /* start of value of a pair */ + if (state.pairStart) { + /* block literals */ + if (stream.match(/^\s*(\||\>)\s*/)) { state.literal = true; return 'meta'; }; + /* references */ + if (stream.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i)) { return 'variable-2'; } + /* numbers */ + if (state.inlinePairs == 0 && stream.match(/^\s*-?[0-9\.\,]+\s?$/)) { return 'number'; } + if (state.inlinePairs > 0 && stream.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/)) { return 'number'; } + /* keywords */ + if (stream.match(keywordRegex)) { return 'keyword'; } + } + + /* nothing found, continue */ + state.pairStart = false; + state.escaped = (ch == '\\'); + stream.next(); + return null; + }, + startState: function() { + return { + pair: false, + pairStart: false, + keyCol: 0, + inlinePairs: 0, + inlineList: 0, + literal: false, + escaped: false + }; + } + }; +}); + +CodeMirror.defineMIME("text/x-yaml", "yaml"); diff --git a/app/src/main/assets/readme_style.css b/app/src/main/assets/readme_style.css new file mode 100644 index 0000000000000000000000000000000000000000..eda436c5384218607e073e99cc2491e36a470113 --- /dev/null +++ b/app/src/main/assets/readme_style.css @@ -0,0 +1,632 @@ +/* + Gollum v3 Template +*/ + +/* margin & padding reset*/ + +html, body { + color: #333; +} + +body { + font: 13.34px helvetica,arial,freesans,clean,sans-serif; + line-height: 1.4; +} + +img { + padding:0px; + margin:0px; + border:0; + width:98%; + text-align:center; +} + +a { + color: #4183C4; + width:90%; + word-wrap:break-word; + text-decoration: none; +} + +a.absent { + color: #c00; +} + +.markdown-body a[id].wiki-toc-anchor { + color: inherit; + text-decoration: none; +} + +.markdown-body { + font-size: 14px; + line-height: 1.6; +} +.markdown-body>*:first-child { + margin-top: 0!important; +} +.markdown-body>*:last-child { + margin-bottom: 0!important; +} +.markdown-body a.absent { + color: #c00; +} +.markdown-body a.anchor { + display: block; + padding-left: 30px; + margin-left: -30px; + cursor: pointer; + position: absolute; + top: 0; + left: 0; + bottom: 0; +} +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin: 20px 0 10px; + padding: 0; + font-weight: bold; + -webkit-font-smoothing: antialiased; + cursor: text; + position: relative; +} +.markdown-body h1:hover a.anchor, +.markdown-body h2:hover a.anchor, +.markdown-body h3:hover a.anchor, +.markdown-body h4:hover a.anchor, +.markdown-body h5:hover a.anchor, +.markdown-body h6:hover a.anchor { + background: url(../images/pin-20.png) no-repeat left center; + text-decoration: none; +} +.markdown-body h1 tt, +.markdown-body h1 code, +.markdown-body h2 tt, +.markdown-body h2 code, +.markdown-body h3 tt, +.markdown-body h3 code, +.markdown-body h4 tt, +.markdown-body h4 code, +.markdown-body h5 tt, +.markdown-body h5 code, +.markdown-body h6 tt, +.markdown-body h6 code { + font-size: inherit; +} +.markdown-body h1 { + font-size: 24px; + color: #000; + margin-top: 20px; + margin-bottom: 10px; +} +.markdown-body h2 { + font-size: 20px; + border-bottom: 1px solid #ccc; + color: #000; +} +.markdown-body h3 { + font-size: 18px; +} +.markdown-body h4 { + font-size: 16px; +} +.markdown-body h5 { + font-size: 14px; +} +.markdown-body h6 { + color: #777; + font-size: 14px; +} +.markdown-body p, +.markdown-body blockquote, +.markdown-body ul, +.markdown-body ol, +.markdown-body dl, +.markdown-body table, +.markdown-body pre, +.markdown-body hr { + margin: 0px 0; + margin-bottom: 15px; +} +.markdown-body li { + margin: 0px; +} +.markdown-body hr { + background: transparent url(../images/dirty-shade.png) repeat-x 0 0; + border: 0 none; + color: #ccc; + height: 4px; + padding: 0; +} +.markdown-body>h1:first-child, +.markdown-body>h2:first-child, +.markdown-body>h3:first-child, +.markdown-body>h4:first-child, +.markdown-body>h5:first-child, +.markdown-body>h6:first-child { +} +.markdown-body h1+h2+h3{ + margin-top: 30px; +} +.markdown-body a:first-child h1, +.markdown-body a:first-child h2, +.markdown-body a:first-child h3, +.markdown-body a:first-child h4, +.markdown-body a:first-child h5, +.markdown-body a:first-child h6 { + margin-top: 0; + padding-top: 0; +} +.markdown-body h1+p, +.markdown-body h2+p, +.markdown-body h3+p, +.markdown-body h4+p, +.markdown-body h5+p, +.markdown-body h6+p { + margin-top: 0; +} +.markdown-body li p.first { + display: inline-block; +} +.markdown-body ul, +.markdown-body ol { + padding-left: 30px; +} +.markdown-body ul li>:first-child, +.markdown-body ol li>:first-child { + margin-top: 0; +} +.markdown-body ul li>:last-child, +.markdown-body ol li>:last-child { + margin-bottom: 0; +} +.markdown-body dl { + padding: 0; +} +.markdown-body dl dt { + font-size: 14px; + font-weight: bold; + font-style: italic; + padding: 0; + margin: 15px 0 5px; +} +.markdown-body dl dt:first-child { + padding: 0; +} +.markdown-body dl dt>:first-child { + margin-top: 0; +} +.markdown-body dl dt>:last-child { + margin-bottom: 0; +} +.markdown-body dl dd { + margin: 0 0 15px; + padding: 0 15px; +} +.markdown-body dl dd>:first-child { + margin-top: 0; +} +.markdown-body dl dd>:last-child { + margin-bottom: 0; +} +.markdown-body blockquote { + border-left: 4px solid #DDD; + padding: 0 15px; + color: #777; +} +.markdown-body blockquote>:first-child { + margin-top: 0; +} +.markdown-body blockquote>:last-child { + margin-bottom: 0; +} +.markdown-body table { + padding: 0; + border-collapse: collapse; + border-spacing: 0; +} +.markdown-body table tr { + border-top: 1px solid #ccc; + background-color: #fff; + margin: 0; + padding: 0; +} +.markdown-body table tr:nth-child(2n) { + background-color: #f8f8f8; +} +.markdown-body table tr th { + font-weight: bold; +} +.markdown-body table tr th, +.markdown-body table tr td { + border: 1px solid #ccc; + text-align: left; + margin: 0; + padding: 6px 13px; +} +.markdown-body table tr th>:first-child, +.markdown-body table tr td>:first-child { + margin-top: 0; +} +.markdown-body table tr th>:last-child, +.markdown-body table tr td>:last-child { + margin-bottom: 0; +} +.markdown-body img { + max-width: 100%; +} +.markdown-body span.frame { + display: block; + overflow: hidden; +} +.markdown-body span.frame>span { + border: 1px solid #ddd; + display: block; + float: left; + overflow: hidden; + margin: 13px 0 0; + padding: 7px; + width: auto; +} +.markdown-body span.frame span img { + display: block; + float: left; +} +.markdown-body span.frame span span { + clear: both; + color: #333; + display: block; + padding: 5px 0 0; +} +.markdown-body span.align-center { + display: block; + overflow: hidden; + clear: both; +} +.markdown-body span.align-center>span { + display: block; + overflow: hidden; + margin: 13px auto 0; + text-align: center; +} +.markdown-body span.align-center span img { + margin: 0 auto; + text-align: center; +} +.markdown-body span.align-right { + display: block; + overflow: hidden; + clear: both; +} +.markdown-body span.align-right>span { + display: block; + overflow: hidden; + margin: 13px 0 0; + text-align: right; +} +.markdown-body span.align-right span img { + margin: 0; + text-align: right; +} +.markdown-body span.float-left { + display: block; + margin-right: 13px; + overflow: hidden; + float: left; +} +.markdown-body span.float-left span { + margin: 13px 0 0; +} +.markdown-body span.float-right { + display: block; + margin-left: 13px; + overflow: hidden; + float: right; +} +.markdown-body span.float-right>span { + display: block; + overflow: hidden; + margin: 13px auto 0; + text-align: right; +} +.markdown-body code, +.markdown-body tt { + margin: 0 2px; + padding: 0 5px; + white-space: nowrap; + border: 1px solid #ddd; + background-color: #f8f8f8; + border-radius: 3px; +} +.markdown-body pre>tt, +.markdown-body pre>code { + margin: 0; + padding: 0; + white-space: pre; + border: none; + background: transparent; +} +.markdown-body pre { + background-color: #f8f8f8; + border: 1px solid #ccc; + font-size: 13px; + line-height: 19px; + overflow: auto; + padding: 6px 10px; + border-radius: 3px; +} +.markdown-body pre pre, +.markdown-body pre code, +.markdown-body pre tt { + background-color: transparent; + border: none; +} +.markdown-body pre pre { + margin: 0; + padding: 0; +} +.toc { + background-color: #F7F7F7; + border: 1px solid #ddd; + padding: 5px 10px; + margin: 0; + border-radius: 3px; +} +.toc-title { + color: #888; + font-size: 14px; + line-height: 1.6; + padding: 2px; + border-bottom: 1px solid #ddd; + margin-bottom: 3px; +} +.toc ul { + padding-left: 10px; + margin: 0; +} +.toc>ul { + margin-left: 10px; + font-size: 17px; +} +.toc ul ul { + font-size: 15px; +} +.toc ul ul ul { + font-size: 14px; +} +.toc ul li{ + margin: 0; +} +#header-content .toc, +#footer-content .toc, +#sidebar-content .toc { + border: none; +} +.highlight { + background: #fff; +} +.highlight .c { + color: #998; + font-style: italic; +} +.highlight .err { + color: #a61717; + background-color: #e3d2d2; +} +.highlight .k { + font-weight: bold; +} +.highlight .o { + font-weight: bold; +} +.highlight .cm { + color: #998; + font-style: italic; +} +.highlight .cp { + color: #999; + font-weight: bold; +} +.highlight .c1 { + color: #998; + font-style: italic; +} +.highlight .cs { + color: #999; + font-weight: bold; + font-style: italic; +} +.highlight .gd { + color: #000; + background-color: #fdd; +} +.highlight .gd .x { + color: #000; + background-color: #faa; +} +.highlight .ge { + font-style: italic; +} +.highlight .gr { + color: #a00; +} +.highlight .gh { + color: #999; +} +.highlight .gi { + color: #000; + background-color: #dfd; +} +.highlight .gi .x { + color: #000; + background-color: #afa; +} +.highlight .go { + color: #888; +} +.highlight .gp { + color: #555; +} +.highlight .gs { + font-weight: bold; +} +.highlight .gu { + color: #800080; + font-weight: bold; +} +.highlight .gt { + color: #a00; +} +.highlight .kc { + font-weight: bold; +} +.highlight .kd { + font-weight: bold; +} +.highlight .kn { + font-weight: bold; +} +.highlight .kp { + font-weight: bold; +} +.highlight .kr { + font-weight: bold; +} +.highlight .kt { + color: #458; + font-weight: bold; +} +.highlight .m { + color: #099; +} +.highlight .s { + color: #d14; +} +.highlight .na { + color: #008080; +} +.highlight .nb { + color: #0086B3; +} +.highlight .nc { + color: #458; + font-weight: bold; +} +.highlight .no { + color: #008080; +} +.highlight .ni { + color: #800080; +} +.highlight .ne { + color: #900; + font-weight: bold; +} +.highlight .nf { + color: #900; + font-weight: bold; +} +.highlight .nn { + color: #555; +} +.highlight .nt { + color: #000080; +} +.highlight .nv { + color: #008080; +} +.highlight .ow { + font-weight: bold; +} +.highlight .w { + color: #bbb; +} +.highlight .mf { + color: #099; +} +.highlight .mh { + color: #099; +} +.highlight .mi { + color: #099; +} +.highlight .mo { + color: #099; +} +.highlight .sb { + color: #d14; +} +.highlight .sc { + color: #d14; +} +.highlight .sd { + color: #d14; +} +.highlight .s2 { + color: #d14; +} +.highlight .se { + color: #d14; +} +.highlight .sh { + color: #d14; +} +.highlight .si { + color: #d14; +} +.highlight .sx { + color: #d14; +} +.highlight .sr { + color: #009926; +} +.highlight .s1 { + color: #d14; +} +.highlight .ss { + color: #990073; +} +.highlight .bp { + color: #999; +} +.highlight .vc { + color: #008080; +} +.highlight .vg { + color: #008080; +} +.highlight .vi { + color: #008080; +} +.highlight .il { + color: #099; +} +.highlight .gc { + color: #999; + background-color: #EAF2F5; +} +.type-csharp .highlight .k { + color: #00F; +} +.type-csharp .highlight .kt { + color: #00F; +} +.type-csharp .highlight .nf { + color: #000; + font-weight: normal; +} +.type-csharp .highlight .nc { + color: #2B91AF; +} +.type-csharp .highlight .nn { + color: #000; +} +.type-csharp .highlight .s { + color: #A31515; +} +.type-csharp .highlight .sc { + color: #A31515; +} diff --git a/app/src/main/assets/source-editor.css b/app/src/main/assets/source-editor.css new file mode 100644 index 0000000000000000000000000000000000000000..781d7b0f9c9599eb929214c7fa4cef23b1cbd901 --- /dev/null +++ b/app/src/main/assets/source-editor.css @@ -0,0 +1,24 @@ +body { + padding: 0; + margin: 0; + font-size: 12px; +} + +.CodeMirror { + overflow-x: scroll; + line-height: 1.4em; +} + +.CodeMirror-scroll { + height: auto; + overflow-y: hidden; +} + +img { + display: block; + border: 1px solid #999; + padding: 1px; + margin-top: 10px; + margin-left: 10px; + background: url("trans_bg.gif") right bottom #EEE; +} diff --git a/app/src/main/assets/source-editor.html b/app/src/main/assets/source-editor.html new file mode 100644 index 0000000000000000000000000000000000000000..b105dd8e0c5adede803820011e0f2f1994737acb --- /dev/null +++ b/app/src/main/assets/source-editor.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/source-editor.js b/app/src/main/assets/source-editor.js new file mode 100644 index 0000000000000000000000000000000000000000..91d031d56a4e3625e45496319e4e44bd53c7e532 --- /dev/null +++ b/app/src/main/assets/source-editor.js @@ -0,0 +1,177 @@ +function getExtension(name) { + if (!name) + return null; + + var lastDot = name.lastIndexOf("."); + if (lastDot == -1 || lastDot + 1 == name.length) + return null; + else + return name.substring(lastDot + 1).toLowerCase(); +} + +function getMode(extension) { + var mode = {}; + if (!extension) + return mode; + + switch (extension) { + case "cc": + case "h": + mode.mode = "text/x-csrc"; + mode.file = "clike"; + break; + case "clj": + mode.mode = "text/x-clojure"; + mode.file = "clojure"; + break; + case "coffee": + mode.mode = "text/x-coffeescript"; + mode.file = "coffeescript"; + break; + case "cpp": + case "c": + mode.mode = "text/x-c++src"; + mode.file = "clike"; + break; + case "cs": + mode.mode = "text/x-csharp"; + mode.file = "clike"; + break; + case "css": + case "sass": + case "scss": + mode.mode = "text/css"; + mode.file = "css"; + break; + case "erl": + mode.mode = "text/x-erlang"; + mode.file = "erlang"; + break; + case "hs": + case "hsc": + mode.mode = "text/x-haskell"; + mode.file = "haskell"; + break; + case "html": + mode.mode = "text/html"; + mode.file = "htmlmixed"; + break; + case "ini": + case "prefs": + mode.mode = "text/x-properties"; + mode.file = "properties"; + break; + case "java": + mode.mode = "text/x-java"; + mode.file = "clike"; + break; + case "gyp": + case "js": + case "json": + mode.mode = "text/javascript"; + mode.file = "javascript"; + break; + case "md": + case "markdown": + mode.mode = "gfm"; + mode.file = "gfm"; + break; + case "pl": + mode.mode = "text/x-perl"; + mode.file = "perl"; + break; + case "py": + mode.mode = "text/x-python"; + mode.file = "python"; + break; + case "r": + mode.mode = "text/x-rsrc"; + mode.file = extension; + break; + case "rb": + mode.mode = "text/x-ruby"; + mode.file = "ruby"; + break; + case "sh": + case "zsh": + mode.mode = "text/x-sh"; + mode.file = "shell"; + break; + case "sql": + mode.mode = "text/x-mysql"; + mode.file = "mysql"; + break; + case "xq": + case "xqy": + case "xquery": + mode.mode = "application/xquery"; + mode.file = "xquery"; + break; + case "project": + case "classpath": + case "xib": + case "xml": + mode.mode = "application/xml"; + mode.file = "xml"; + break; + case "yml": + mode.mode = "text/x-yaml"; + mode.file = "yaml"; + break; + default: + mode.mode = "text/x-" + extension; + mode.file = extension; + } + return mode; +} + +function updateWidth() { + var lines = document.getElementsByClassName("CodeMirror-lines")[0]; + if (lines) { + var root = document.getElementsByClassName("CodeMirror")[0]; + if (root && lines.scrollWidth > lines.clientWidth) + root.style.width = lines.scrollWidth + "px"; + } +} + +function loadImage(type, content) { + var img = document.createElement("img"); + img.setAttribute("src", "data:image/" + type + ";base64," + content); + document.body.appendChild(img); +} + +window.onload = function () { + var name = new String(SourceEditor.getName()); + var extension = getExtension(name); + if ("png" == extension || "gif" == extension) { + loadImage(extension, SourceEditor.getRawContent()); + return; + } else if ("jpg" == extension || "jpeg" == extension) { + loadImage("jpeg", SourceEditor.getRawContent()); + return; + } + + CodeMirror.modeURL = "mode/%N/%N.js"; + + var config = {}; + config.value = new String(SourceEditor.getContent()); + config.readOnly = "nocursor"; + config.lineNumbers = true; + config.autofocus = false; + config.lineWrapping = !!SourceEditor.getWrap(); + config.dragDrop = false; + var editor = CodeMirror(document.body, config); + + + var mode = getMode(extension); + if (mode.mode) + editor.setOption("mode", mode.mode); + if (mode.file) + CodeMirror.autoLoadMode(editor, mode.file); + + if (!config.lineWrapping) + updateWidth(); + + editor.refresh(); + +}; diff --git a/app/src/main/assets/sub_tab_original.json b/app/src/main/assets/sub_tab_original.json new file mode 100644 index 0000000000000000000000000000000000000000..941435ed281fbc12cfdaf077eed5bccf9ddb8bb3 --- /dev/null +++ b/app/src/main/assets/sub_tab_original.json @@ -0,0 +1,325 @@ +[ + { + "banner": { + "catalog": 1, + "href": "https://www.oschina.net/action/apiv2//banner?catalog=1" + }, + "fixed": true, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=d6112fa662bc4bf21084670a857fbd20", + "name": "开源资讯", + "needLogin": false, + "isActived": true, + "order": 1, + "subtype": 1, + "token": "d6112fa662bc4bf21084670a857fbd20", + "type": 6 + }, + { + "fixed": true, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=df985be3c5d5449f8dfb47e06e098ef9", + "name": "推荐博客", + "needLogin": false, + "isActived": true, + "order": 3, + "subtype": 4, + "token": "df985be3c5d5449f8dfb47e06e098ef9", + "type": 3 + }, + { + "fixed": true, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=98d04eb58a1d12b75d254deecbc83790", + "name": "技术问答", + "needLogin": false, + "isActived": true, + "order": 2, + "subtype": 3, + "token": "98d04eb58a1d12b75d254deecbc83790", + "type": 2 + }, + { + "fixed": true, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=1abf09a23a87442184c2f9bf9dc29e35", + "name": "每日一博", + "needLogin": false, + "isActived": true, + "order": 4, + "subtype": 1, + "token": "1abf09a23a87442184c2f9bf9dc29e35", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=299975006aa7f9b8a158deeb0d27a011", + "name": "码云推荐", + "needLogin": false, + "isActived": false, + "order": 70, + "subtype": 1, + "token": "299975006aa7f9b8a158deeb0d27a011", + "type": 7 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=e3e35d14a62b4f816ec878b6597b60aa", + "name": "热门资讯", + "needLogin": false, + "isActived": false, + "order": 71, + "subtype": 2, + "token": "e3e35d14a62b4f816ec878b6597b60aa", + "type": 6 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=177f171d41bb37d80c67bee14123f7e1", + "name": "最新翻译", + "needLogin": false, + "isActived": false, + "order": 74, + "subtype": 1, + "token": "177f171d41bb37d80c67bee14123f7e1", + "type": 4 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=5718df855db5913f25cab8ee4f7c656c", + "name": "移动开发", + "needLogin": false, + "isActived": false, + "order": 75, + "subtype": 18, + "token": "5718df855db5913f25cab8ee4f7c656c", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=7975ce15d909bb61a6b85b9392db6149", + "name": "开源硬件", + "needLogin": false, + "isActived": false, + "order": 76, + "subtype": 17, + "token": "7975ce15d909bb61a6b85b9392db6149", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=99838268b3bc311a2e763a6a2c4bb9ec", + "name": "云计算", + "needLogin": false, + "isActived": false, + "order": 77, + "subtype": 16, + "token": "99838268b3bc311a2e763a6a2c4bb9ec", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=ce53a71a371867582eb3023415c83489", + "name": "软件工程", + "needLogin": false, + "isActived": false, + "order": 78, + "subtype": 15, + "token": "ce53a71a371867582eb3023415c83489", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=253b519335b59c011b6db7d1165932bf", + "name": "系统运维", + "needLogin": false, + "isActived": false, + "order": 79, + "subtype": 14, + "token": "253b519335b59c011b6db7d1165932bf", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=ad0f1d9e413aeadde89eb899817edd81", + "name": "图像多媒体", + "needLogin": false, + "isActived": false, + "order": 80, + "subtype": 13, + "token": "ad0f1d9e413aeadde89eb899817edd81", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=1d76f396e1aad15cf167754bfba9ca78", + "name": "企业开发", + "needLogin": false, + "isActived": false, + "order": 81, + "subtype": 12, + "token": "1d76f396e1aad15cf167754bfba9ca78", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=471587b6f0a943f4d675281fa5d13975", + "name": "数据库", + "needLogin": false, + "isActived": false, + "order": 82, + "subtype": 11, + "token": "471587b6f0a943f4d675281fa5d13975", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=285cfaccd55f60ebc919d30ad709c6b4", + "name": "编程语言", + "needLogin": false, + "isActived": false, + "order": 83, + "subtype": 10, + "token": "285cfaccd55f60ebc919d30ad709c6b4", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=5ca68e0307f68ccd2d181b07d45b110b", + "name": "游戏开发", + "needLogin": false, + "isActived": false, + "order": 84, + "subtype": 9, + "token": "5ca68e0307f68ccd2d181b07d45b110b", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=32704cb501253b0422544fd84b7de35b", + "name": "服务端开发", + "needLogin": false, + "isActived": false, + "order": 85, + "subtype": 8, + "token": "32704cb501253b0422544fd84b7de35b", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=1299ab1d9f2981cf6100083e106aa531", + "name": "前端开发", + "needLogin": false, + "isActived": false, + "order": 86, + "subtype": 7, + "token": "1299ab1d9f2981cf6100083e106aa531", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=63697c05f04140b1ca1dba8e0822ebf4", + "name": "源创君", + "needLogin": false, + "isActived": false, + "order": 87, + "subtype": 6, + "token": "63697c05f04140b1ca1dba8e0822ebf4", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=0048958a909973b5d83a8eca65d90afb", + "name": "最新博客", + "needLogin": false, + "isActived": false, + "order": 90, + "subtype": 3, + "token": "0048958a909973b5d83a8eca65d90afb", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=b5a06540fdf8d2d4de5c40775585a188", + "name": "热门博客", + "needLogin": false, + "isActived": false, + "order": 91, + "subtype": 2, + "token": "b5a06540fdf8d2d4de5c40775585a188", + "type": 3 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=2953c7a3d4ebfbae2c45fd34698a6545", + "name": "站务建议", + "needLogin": false, + "isActived": false, + "order": 93, + "subtype": 7, + "token": "2953c7a3d4ebfbae2c45fd34698a6545", + "type": 2 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=ce1231d4a15c3d1e944943a6aa54d695", + "name": "职业生涯", + "needLogin": false, + "isActived": false, + "order": 94, + "subtype": 6, + "token": "ce1231d4a15c3d1e944943a6aa54d695", + "type": 2 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=44fe8b07756796cf620dbd18a4cd56e1", + "name": "行业杂烩", + "needLogin": false, + "isActived": false, + "order": 95, + "subtype": 5, + "token": "44fe8b07756796cf620dbd18a4cd56e1", + "type": 2 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=b56759c46cb81192b7d1cbb1a478a45d", + "name": "技术分享", + "needLogin": false, + "isActived": false, + "order": 96, + "subtype": 4, + "token": "b56759c46cb81192b7d1cbb1a478a45d", + "type": 2 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=8f7029b477abf806b1125aff3a666e80", + "name": "开源访谈", + "needLogin": false, + "isActived": false, + "order": 98, + "subtype": 2, + "token": "8f7029b477abf806b1125aff3a666e80", + "type": 2 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=8ebb873ebbdb5f60d33db3f7dd15d63b", + "name": "高手问答", + "needLogin": false, + "isActived": false, + "order": 99, + "subtype": 1, + "token": "8ebb873ebbdb5f60d33db3f7dd15d63b", + "type": 2 + }, + { + "fixed": false, + "href": "https://www.oschina.net/action/apiv2/sub_list?token=b4ca1962b3a80823c6138441015d9836", + "name": "最新软件", + "needLogin": false, + "isActived": false, + "order": 100, + "subtype": 1, + "token": "b4ca1962b3a80823c6138441015d9836", + "type": 1 + } +] diff --git a/app/src/main/java/com/dtr/zxing/activity/CaptureActivity.java b/app/src/main/java/com/dtr/zxing/activity/CaptureActivity.java index abc909303f7cb5db7ba4568f91290791fd6108fa..dde3f3bb736d8fb1f25bd26493c6f7d4b4552efa 100644 --- a/app/src/main/java/com/dtr/zxing/activity/CaptureActivity.java +++ b/app/src/main/java/com/dtr/zxing/activity/CaptureActivity.java @@ -15,13 +15,14 @@ */ package com.dtr.zxing.activity; +import android.Manifest; import android.annotation.SuppressLint; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; +import android.support.annotation.NonNull; import android.text.ClipboardManager; import android.util.Log; import android.view.SurfaceHolder; @@ -33,15 +34,17 @@ import android.view.animation.Animation; import android.view.animation.TranslateAnimation; import android.widget.ImageView; import android.widget.RelativeLayout; -import cz.msebera.android.httpclient.Header; +import android.widget.Toast; import com.dtr.zxing.camera.CameraManager; import com.dtr.zxing.decode.DecodeThread; import com.dtr.zxing.utils.BeepManager; import com.dtr.zxing.utils.CaptureActivityHandler; import com.dtr.zxing.utils.InactivityTimer; +import com.google.gson.reflect.TypeToken; import com.google.zxing.Result; import com.loopj.android.http.AsyncHttpResponseHandler; +import com.loopj.android.http.TextHttpResponseHandler; import net.oschina.app.AppContext; import net.oschina.app.AppException; @@ -51,14 +54,26 @@ import net.oschina.app.base.BaseActivity; import net.oschina.app.bean.BarCode; import net.oschina.app.bean.ResultBean; import net.oschina.app.bean.SingInResult; -import net.oschina.app.util.DialogHelp; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.user.activities.UserEventSigninActivity; +import net.oschina.app.improve.user.sign.up.SignUpInfoActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.SimplexToast; import net.oschina.app.util.StringUtils; import net.oschina.app.util.UIHelper; import net.oschina.app.util.XmlUtils; - import java.io.IOException; import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; + +import cz.msebera.android.httpclient.Header; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; /** * This activity opens the camera and does the actual scanning on a background @@ -70,7 +85,7 @@ import java.lang.reflect.Field; * @author Sean Owen */ public final class CaptureActivity extends BaseActivity implements - SurfaceHolder.Callback { + SurfaceHolder.Callback, EasyPermissions.PermissionCallbacks { private static final String TAG = CaptureActivity.class.getSimpleName(); @@ -82,21 +97,10 @@ public final class CaptureActivity extends BaseActivity implements private SurfaceView scanPreview = null; private RelativeLayout scanContainer; private RelativeLayout scanCropView; - private ImageView scanLine; private ImageView mFlash; private Rect mCropRect = null; - public Handler getHandler() { - return handler; - } - - public CameraManager getCameraManager() { - return cameraManager; - } - - private boolean isHasSurface = false; - @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -106,66 +110,28 @@ public final class CaptureActivity extends BaseActivity implements setContentView(R.layout.activity_qr_scan); scanPreview = (SurfaceView) findViewById(R.id.capture_preview); - scanContainer = (RelativeLayout) findViewById(R.id.capture_container); - scanCropView = (RelativeLayout) findViewById(R.id.capture_crop_view); - scanLine = (ImageView) findViewById(R.id.capture_scan_line); - mFlash = (ImageView) findViewById(R.id.capture_flash); - mFlash.setOnClickListener(this); - - inactivityTimer = new InactivityTimer(this); - beepManager = new BeepManager(this); - - TranslateAnimation animation = new TranslateAnimation( - Animation.RELATIVE_TO_PARENT, 0.0f, - Animation.RELATIVE_TO_PARENT, 0.0f, - Animation.RELATIVE_TO_PARENT, 0.0f, - Animation.RELATIVE_TO_PARENT, 0.9f); - animation.setDuration(4500); - animation.setRepeatCount(-1); - animation.setRepeatMode(Animation.RESTART); - scanLine.startAnimation(animation); - } - - @SuppressLint("NewApi") - @Override - protected boolean hasActionBar() { - - if (android.os.Build.VERSION.SDK_INT >= 11) { - getSupportActionBar().hide(); - return true; - } else { - return false; - } + // Install the callback and wait for surfaceCreated() to init the + // camera. + scanPreview.getHolder().addCallback(this); + cameraTask(); } @Override protected void onResume() { - super.onResume(); - - // CameraManager must be initialized here, not in onCreate(). This is - // necessary because we don't - // want to open the camera driver and measure the screen size if we're - // going to show the help on - // first launch. That led to bugs where the scanning rectangle was the - // wrong size and partially - // off screen. - cameraManager = new CameraManager(getApplication()); - - handler = null; - - if (isHasSurface) { - // The activity was paused but not stopped, so the surface still - // exists. Therefore - // surfaceCreated() won't be called, so init the camera here. - initCamera(scanPreview.getHolder()); - } else { - // Install the callback and wait for surfaceCreated() to init the - // camera. - scanPreview.getHolder().addCallback(this); + if (scanPreview != null) { + handler = null; + if (isHasSurface) { + // The activity was paused but not stopped, so the surface still + // exists. Therefore + // surfaceCreated() won't be called, so init the camera here. + initCamera(scanPreview.getHolder()); + } } - - inactivityTimer.onResume(); + if (inactivityTimer != null) { + inactivityTimer.onResume(); + } + super.onResume(); } @Override @@ -174,31 +140,57 @@ public final class CaptureActivity extends BaseActivity implements handler.quitSynchronously(); handler = null; } - inactivityTimer.onPause(); - beepManager.close(); - cameraManager.closeDriver(); - if (!isHasSurface) { - scanPreview.getHolder().removeCallback(this); + if (inactivityTimer != null) { + inactivityTimer.onPause(); + } + if (beepManager != null) { + beepManager.close(); + } + if (cameraManager != null) { + cameraManager.closeDriver(); } super.onPause(); } @Override protected void onDestroy() { - inactivityTimer.shutdown(); + if (inactivityTimer != null) { + inactivityTimer.shutdown(); + } + if (scanPreview != null) { + scanPreview.getHolder().removeCallback(this); + } super.onDestroy(); } + public Handler getHandler() { + return handler; + } + + public CameraManager getCameraManager() { + return cameraManager; + } + + private boolean isHasSurface = false; + + @SuppressLint("NewApi") + @Override + protected boolean hasActionBar() { + if (android.os.Build.VERSION.SDK_INT >= 11) { + getSupportActionBar().hide(); + return true; + } else { + return false; + } + } + @Override public void surfaceCreated(SurfaceHolder holder) { if (holder == null) { - Log.e(TAG, - "*** WARNING *** surfaceCreated() gave us a null surface!"); - } - if (!isHasSurface) { - isHasSurface = true; - initCamera(holder); + Log.e(TAG, "*** WARNING *** surfaceCreated() gave us a null surface!"); } + isHasSurface = true; + initCamera(holder); } @Override @@ -209,7 +201,7 @@ public final class CaptureActivity extends BaseActivity implements @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - + // Doing } /** @@ -224,12 +216,12 @@ public final class CaptureActivity extends BaseActivity implements beepManager.playBeepSoundAndVibrate(); // 通过这种方式可以获取到扫描的图片 -// bundle.putInt("width", mCropRect.width()); -// bundle.putInt("height", mCropRect.height()); -// bundle.putString("result", rawResult.getText()); -// -// startActivity(new Intent(CaptureActivity.this, ResultActivity.class) -// .putExtras(bundle)); + // bundle.putInt("width", mCropRect.width()); + // bundle.putInt("height", mCropRect.height()); + // bundle.putString("result", rawResult.getText()); + // + // startActivity(new Intent(CaptureActivity.this, ResultActivity.class) + // .putExtras(bundle)); handler.postDelayed(new Runnable() { @@ -254,12 +246,55 @@ public final class CaptureActivity extends BaseActivity implements showConfirmLogin(url); return; } + + if (url.contains("www.oschina.net/event/signin?event")) { + final long sourceId = Long.valueOf(url.substring(url.indexOf("=") + 1));//2192570;2193441 + if (AccountHelper.isLogin()) { + OSChinaApi.getDetail(5, "", sourceId, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (mFlash != null) { + SimplexToast.show(CaptureActivity.this, "请连接网络再试"); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + net.oschina.app.improve.bean.base.ResultBean resultBean = AppOperator.createGson().fromJson(responseString, + new TypeToken>() { + }.getType()); + if (resultBean.isSuccess()) { + Map extra = resultBean.getResult().getExtra(); + if (Double.valueOf(extra.get("eventApplyStatus").toString()).intValue() != -1) + SignUpInfoActivity.show(CaptureActivity.this, sourceId, 2); + else + EventDetailActivity.show(CaptureActivity.this, sourceId); + } else { + EventDetailActivity.show(CaptureActivity.this, sourceId); + } + finish(); + } catch (Exception e) { + e.printStackTrace(); + EventDetailActivity.show(CaptureActivity.this, sourceId); + finish(); + } + } + }); + } else { + UserEventSigninActivity.show(CaptureActivity.this, sourceId); + finish(); + } + return; + } + if (url.contains("oschina.net")) { UIHelper.showUrlRedirect(CaptureActivity.this, url); finish(); return; } - DialogHelp.getConfirmDialog(this, "可能存在风险,是否打开链接?
    " + url, new DialogInterface.OnClickListener() { + + DialogHelper.getConfirmDialog(this, "可能存在风险,是否打开链接?\n" + url, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { UIHelper.showUrlRedirect(CaptureActivity.this, url); @@ -274,11 +309,11 @@ public final class CaptureActivity extends BaseActivity implements } private void showConfirmLogin(final String url) { - if (!AppContext.getInstance().isLogin()) { + if (!AccountHelper.isLogin()) { showLogin(); return; } - DialogHelp.getConfirmDialog(this, "扫描成功,是否进行网页登陆", new DialogInterface.OnClickListener() { + DialogHelper.getConfirmDialog(this, "扫描成功,是否进行网页登陆", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { handleScanLogin(url); @@ -292,27 +327,34 @@ public final class CaptureActivity extends BaseActivity implements }).show(); } + @SuppressWarnings("deprecation") private void handleScanLogin(final String url) { OSChinaApi.scanQrCodeLogin(url, new AsyncHttpResponseHandler() { @Override public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - ResultBean result = XmlUtils.toBean(ResultBean.class, arg2); - if (result != null && result.getResult() != null - && result.getResult().OK()) { - AppContext.showToast(result.getResult().getErrorMessage()); - finish(); - } else { - handler.sendEmptyMessage(R.id.restart_preview); - AppContext.showToast(result != null - && result.getResult() != null ? result.getResult() - .getErrorMessage() : "登陆失败"); + try { + ResultBean result = XmlUtils.toBean(ResultBean.class, arg2); + if (result != null && result.getResult() != null + && result.getResult().OK()) { + AppContext.showToast(result.getResult().getErrorMessage()); + finish(); + } else { + handler.sendEmptyMessage(R.id.restart_preview); + AppContext.showToast(result != null + && result.getResult() != null ? result.getResult() + .getErrorMessage() : "登陆失败"); + } + } catch (Exception e) { + e.printStackTrace(); } } @Override public void onFailure(int arg0, Header[] arg1, byte[] arg2, Throwable arg3) { + if (mFlash == null) + return; handler.sendEmptyMessage(R.id.restart_preview); if (arg2 != null) { AppContext.showToast(new String(arg2)); @@ -329,6 +371,8 @@ public final class CaptureActivity extends BaseActivity implements @Override public void onFinish() { + if (mFlash == null) + return; super.onFinish(); hideWaitDialog(); } @@ -357,7 +401,7 @@ public final class CaptureActivity extends BaseActivity implements } private void handleSignIn(BarCode barCode) { - if (barCode.isRequireLogin() && !AppContext.getInstance().isLogin()) { + if (barCode.isRequireLogin() && !AccountHelper.isLogin()) { showLogin(); return; } @@ -368,9 +412,9 @@ public final class CaptureActivity extends BaseActivity implements try { SingInResult res = SingInResult.parse(new String(arg2)); if (res.isOk()) { - DialogHelp.getMessageDialog(CaptureActivity.this, res.getMessage()).show(); + DialogHelper.getMessageDialog(CaptureActivity.this, res.getMessage()).show(); } else { - DialogHelp.getMessageDialog(CaptureActivity.this, res.getErrorMes()).show(); + DialogHelper.getMessageDialog(CaptureActivity.this, res.getErrorMes()).show(); } } catch (AppException e) { e.printStackTrace(); @@ -382,7 +426,7 @@ public final class CaptureActivity extends BaseActivity implements public void onFailure(int arg0, Header[] arg1, byte[] arg2, Throwable arg3) { hideWaitDialog(); - DialogHelp.getMessageDialog(CaptureActivity.this, arg3.getMessage()).show(); + DialogHelper.getMessageDialog(CaptureActivity.this, arg3.getMessage()).show(); } @Override @@ -391,11 +435,11 @@ public final class CaptureActivity extends BaseActivity implements hideWaitDialog(); } }; - OSChinaApi.singnIn(barCode.getUrl(), handler); + OSChinaApi.signin(barCode.getUrl(), handler); } private void showLogin() { - DialogHelp.getConfirmDialog(this, "请先登录,再进行", new DialogInterface.OnClickListener() { + DialogHelper.getConfirmDialog(this, "请先登录,再进行", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { UIHelper.showLoginActivity(CaptureActivity.this); @@ -404,7 +448,7 @@ public final class CaptureActivity extends BaseActivity implements } private void showCopyTextOption(final String text) { - DialogHelp.getConfirmDialog(this, text, new DialogInterface.OnClickListener() { + DialogHelper.getConfirmDialog(this, text, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { ClipboardManager cbm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); @@ -421,6 +465,9 @@ public final class CaptureActivity extends BaseActivity implements } private void initCamera(SurfaceHolder surfaceHolder) { + if (cameraManager == null) + return; + if (surfaceHolder == null) { throw new IllegalStateException("No SurfaceHolder provided"); } @@ -439,36 +486,14 @@ public final class CaptureActivity extends BaseActivity implements } initCrop(); - } catch (IOException ioe) { - Log.w(TAG, ioe); - displayFrameworkBugMessageAndExit(); - } catch (RuntimeException e) { + } catch (IOException | RuntimeException e) { Log.w(TAG, "Unexpected error initializing camera", e); displayFrameworkBugMessageAndExit(); } } private void displayFrameworkBugMessageAndExit() { - // camera error - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.app_name)); - builder.setMessage("相机打开出错,请稍后重试"); - builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - - }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() { - - @Override - public void onCancel(DialogInterface dialog) { - finish(); - } - }); - builder.show(); + Toast.makeText(this, R.string.permissions_camera_error, Toast.LENGTH_LONG).show(); } public void restartPreviewAfterDelay(long delayMS) { @@ -484,6 +509,7 @@ public final class CaptureActivity extends BaseActivity implements /** * 初始化截取的矩形区域 */ + @SuppressWarnings("SuspiciousNameCombination") private void initCrop() { int cameraWidth = cameraManager.getCameraResolution().y; int cameraHeight = cameraManager.getCameraResolution().x; @@ -531,42 +557,102 @@ public final class CaptureActivity extends BaseActivity implements @Override public void onClick(View v) { - // TODO Auto-generated method stub switch (v.getId()) { case R.id.capture_flash: light(); break; - default: break; } } - private boolean flag; + private boolean mIsLight; - protected void light() { - if (flag == true) { - flag = false; - // 开闪光灯 - cameraManager.openLight(); - mFlash.setBackgroundResource(R.drawable.flash_open); - } else { - flag = true; - // 关闪光灯 - cameraManager.offLight(); - mFlash.setBackgroundResource(R.drawable.flash_default); + private void light() { + try { + if (mIsLight) { + // 关闪光灯 + cameraManager.offLight(); + mFlash.setBackgroundResource(R.mipmap.flash_default); + mIsLight = false; + } else { + // 开闪光灯 + cameraManager.openLight(); + mFlash.setBackgroundResource(R.mipmap.flash_open); + mIsLight = true; + } + } catch (Exception e) { + e.printStackTrace(); } } @Override public void initView() { - // TODO Auto-generated method stub - } @Override public void initData() { - // TODO Auto-generated method stub + } + private void initCamera() { + scanContainer = (RelativeLayout) findViewById(R.id.capture_container); + scanCropView = (RelativeLayout) findViewById(R.id.capture_crop_view); + ImageView scanLine = (ImageView) findViewById(R.id.capture_scan_line); + mFlash = (ImageView) findViewById(R.id.capture_flash); + mFlash.setOnClickListener(this); + + inactivityTimer = new InactivityTimer(this); + beepManager = new BeepManager(this); + + TranslateAnimation animation = new TranslateAnimation( + Animation.RELATIVE_TO_PARENT, 0.0f, + Animation.RELATIVE_TO_PARENT, 0.0f, + Animation.RELATIVE_TO_PARENT, 0.0f, + Animation.RELATIVE_TO_PARENT, 0.9f); + animation.setDuration(4500); + animation.setRepeatCount(-1); + animation.setRepeatMode(Animation.RESTART); + scanLine.startAnimation(animation); + + cameraManager = new CameraManager(getApplication()); + } + + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + if (perms != null && perms.size() == 2) { + initCamera(); + } else { + displayFrameworkBugMessageAndExit(); + } + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + displayFrameworkBugMessageAndExit(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + // EasyPermissions handles the request result. + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + + private static final int CAMERA_PERM = 1; + + @AfterPermissionGranted(CAMERA_PERM) + private void cameraTask() { + String[] perms = {Manifest.permission.CAMERA, Manifest.permission.VIBRATE}; + if (EasyPermissions.hasPermissions(this, perms)) { + initCamera(); + } else { + // Request one permission + EasyPermissions.requestPermissions(this, + getResources().getString(R.string.str_request_camera_message), + CAMERA_PERM, perms); + } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dtr/zxing/camera/AutoFocusManager.java b/app/src/main/java/com/dtr/zxing/camera/AutoFocusManager.java index 0def96bdbdd7ebd387eaa6ebe02182b2e2d46fcb..e689ae71f813508eb141b48bb51d51783306148f 100644 --- a/app/src/main/java/com/dtr/zxing/camera/AutoFocusManager.java +++ b/app/src/main/java/com/dtr/zxing/camera/AutoFocusManager.java @@ -29,107 +29,107 @@ import java.util.concurrent.RejectedExecutionException; public class AutoFocusManager implements Camera.AutoFocusCallback { - private static final String TAG = AutoFocusManager.class.getSimpleName(); - - private static final long AUTO_FOCUS_INTERVAL_MS = 2000L; - private static final Collection FOCUS_MODES_CALLING_AF; - - static { - FOCUS_MODES_CALLING_AF = new ArrayList(2); - FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_AUTO); - FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_MACRO); - } - - private boolean stopped; - private boolean focusing; - private final boolean useAutoFocus; - private final Camera camera; - private AsyncTask outstandingTask; - - public AutoFocusManager(Context context, Camera camera) { - this.camera = camera; - String currentFocusMode = camera.getParameters().getFocusMode(); - useAutoFocus = FOCUS_MODES_CALLING_AF.contains(currentFocusMode); - Log.i(TAG, "Current focus mode '" + currentFocusMode + "'; use auto focus? " + useAutoFocus); - start(); - } - - @Override - public synchronized void onAutoFocus(boolean success, Camera theCamera) { - focusing = false; - autoFocusAgainLater(); - } - - @SuppressLint("NewApi") - private synchronized void autoFocusAgainLater() { - if (!stopped && outstandingTask == null) { - AutoFocusTask newTask = new AutoFocusTask(); - try { - if (Build.VERSION.SDK_INT >= 11) { - newTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - newTask.execute(); - } - outstandingTask = newTask; - } catch (RejectedExecutionException ree) { - Log.w(TAG, "Could not request auto focus", ree); - } - } - } - - public synchronized void start() { - if (useAutoFocus) { - outstandingTask = null; - if (!stopped && !focusing) { - try { - camera.autoFocus(this); - focusing = true; - } catch (RuntimeException re) { - // Have heard RuntimeException reported in Android 4.0.x+; - // continue? - Log.w(TAG, "Unexpected exception while focusing", re); - // Try again later to keep cycle going - autoFocusAgainLater(); - } - } - } - } - - private synchronized void cancelOutstandingTask() { - if (outstandingTask != null) { - if (outstandingTask.getStatus() != AsyncTask.Status.FINISHED) { - outstandingTask.cancel(true); - } - outstandingTask = null; - } - } - - public synchronized void stop() { - stopped = true; - if (useAutoFocus) { - cancelOutstandingTask(); - // Doesn't hurt to call this even if not focusing - try { - camera.cancelAutoFocus(); - } catch (RuntimeException re) { - // Have heard RuntimeException reported in Android 4.0.x+; - // continue? - Log.w(TAG, "Unexpected exception while cancelling focusing", re); - } - } - } - - private final class AutoFocusTask extends AsyncTask { - @Override - protected Object doInBackground(Object... voids) { - try { - Thread.sleep(AUTO_FOCUS_INTERVAL_MS); - } catch (InterruptedException e) { - // continue - } - start(); - return null; - } - } + private static final String TAG = AutoFocusManager.class.getSimpleName(); + + private static final long AUTO_FOCUS_INTERVAL_MS = 2000L; + private static final Collection FOCUS_MODES_CALLING_AF; + + static { + FOCUS_MODES_CALLING_AF = new ArrayList(2); + FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_AUTO); + FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_MACRO); + } + + private boolean stopped; + private boolean focusing; + private final boolean useAutoFocus; + private final Camera camera; + private AsyncTask outstandingTask; + + public AutoFocusManager(Context context, Camera camera) { + this.camera = camera; + String currentFocusMode = camera.getParameters().getFocusMode(); + useAutoFocus = FOCUS_MODES_CALLING_AF.contains(currentFocusMode); + Log.i(TAG, "Current focus mode '" + currentFocusMode + "'; use auto focus? " + useAutoFocus); + start(); + } + + @Override + public synchronized void onAutoFocus(boolean success, Camera theCamera) { + focusing = false; + autoFocusAgainLater(); + } + + @SuppressLint("NewApi") + private synchronized void autoFocusAgainLater() { + if (!stopped && outstandingTask == null) { + AutoFocusTask newTask = new AutoFocusTask(); + try { + if (Build.VERSION.SDK_INT >= 11) { + newTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + newTask.execute(); + } + outstandingTask = newTask; + } catch (RejectedExecutionException ree) { + Log.w(TAG, "Could not request auto focus", ree); + } + } + } + + public synchronized void start() { + if (useAutoFocus) { + outstandingTask = null; + if (!stopped && !focusing) { + try { + camera.autoFocus(this); + focusing = true; + } catch (RuntimeException re) { + // Have heard RuntimeException reported in Android 4.0.x+; + // continue? + Log.w(TAG, "Unexpected exception while focusing", re); + // Try again later to keep cycle going + autoFocusAgainLater(); + } + } + } + } + + private synchronized void cancelOutstandingTask() { + if (outstandingTask != null) { + if (outstandingTask.getStatus() != AsyncTask.Status.FINISHED) { + outstandingTask.cancel(true); + } + outstandingTask = null; + } + } + + public synchronized void stop() { + stopped = true; + if (useAutoFocus) { + cancelOutstandingTask(); + // Doesn't hurt to call this even if not focusing + try { + camera.cancelAutoFocus(); + } catch (RuntimeException re) { + // Have heard RuntimeException reported in Android 4.0.x+; + // continue? + Log.w(TAG, "Unexpected exception while cancelling focusing", re); + } + } + } + + private final class AutoFocusTask extends AsyncTask { + @Override + protected Object doInBackground(Object... voids) { + try { + Thread.sleep(AUTO_FOCUS_INTERVAL_MS); + } catch (InterruptedException e) { + // continue + } + start(); + return null; + } + } } diff --git a/app/src/main/java/com/dtr/zxing/camera/CameraConfigurationManager.java b/app/src/main/java/com/dtr/zxing/camera/CameraConfigurationManager.java index 787f86ffbaf517e6d3ec19e277476960aad3158a..0f4760015cd35cafebe79b452f0f1459e1c12e7f 100644 --- a/app/src/main/java/com/dtr/zxing/camera/CameraConfigurationManager.java +++ b/app/src/main/java/com/dtr/zxing/camera/CameraConfigurationManager.java @@ -30,197 +30,195 @@ import java.util.Iterator; import java.util.List; /** - * * 邮箱: 1076559197@qq.com | tauchen1990@gmail.com - * + *

    * 作者: 陈涛 - * + *

    * 日期: 2014年8月20日 - * + *

    * 描述: 该类主要负责设置相机的参数信息,获取最佳的预览界面 - * */ public final class CameraConfigurationManager { - private static final String TAG = "CameraConfiguration"; - - private static final int MIN_PREVIEW_PIXELS = 480 * 320; - private static final double MAX_ASPECT_DISTORTION = 0.15; - - private final Context context; - - // 屏幕分辨率 - private Point screenResolution; - // 相机分辨率 - private Point cameraResolution; - - public CameraConfigurationManager(Context context) { - this.context = context; - } - - public void initFromCameraParameters(Camera camera) { - Camera.Parameters parameters = camera.getParameters(); - WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - Display display = manager.getDefaultDisplay(); - Point theScreenResolution = new Point(); - theScreenResolution = getDisplaySize(display); - - screenResolution = theScreenResolution; - Log.i(TAG, "Screen resolution: " + screenResolution); - - /** 因为换成了竖屏显示,所以不替换屏幕宽高得出的预览图是变形的 */ - Point screenResolutionForCamera = new Point(); - screenResolutionForCamera.x = screenResolution.x; - screenResolutionForCamera.y = screenResolution.y; - - if (screenResolution.x < screenResolution.y) { - screenResolutionForCamera.x = screenResolution.y; - screenResolutionForCamera.y = screenResolution.x; - } - - cameraResolution = findBestPreviewSizeValue(parameters, screenResolutionForCamera); - Log.i(TAG, "Camera resolution x: " + cameraResolution.x); - Log.i(TAG, "Camera resolution y: " + cameraResolution.y); - } - - @SuppressWarnings("deprecation") - @SuppressLint("NewApi") - private Point getDisplaySize(final Display display) { - final Point point = new Point(); - try { - display.getSize(point); - } catch (NoSuchMethodError ignore) { - point.x = display.getWidth(); - point.y = display.getHeight(); - } - return point; - } - - public void setDesiredCameraParameters(Camera camera, boolean safeMode) { - Camera.Parameters parameters = camera.getParameters(); - - if (parameters == null) { - Log.w(TAG, "Device error: no camera parameters are available. Proceeding without configuration."); - return; - } - - Log.i(TAG, "Initial camera parameters: " + parameters.flatten()); - - if (safeMode) { - Log.w(TAG, "In camera config safe mode -- most settings will not be honored"); - } - - parameters.setPreviewSize(cameraResolution.x, cameraResolution.y); - camera.setParameters(parameters); - - Camera.Parameters afterParameters = camera.getParameters(); - Camera.Size afterSize = afterParameters.getPreviewSize(); - if (afterSize != null && (cameraResolution.x != afterSize.width || cameraResolution.y != afterSize.height)) { - Log.w(TAG, "Camera said it supported preview size " + cameraResolution.x + 'x' + cameraResolution.y + ", but after setting it, preview size is " + afterSize.width + 'x' + afterSize.height); - cameraResolution.x = afterSize.width; - cameraResolution.y = afterSize.height; - } - - /** 设置相机预览为竖屏 */ - camera.setDisplayOrientation(90); - } - - public Point getCameraResolution() { - return cameraResolution; - } - - public Point getScreenResolution() { - return screenResolution; - } - - /** - * 从相机支持的分辨率中计算出最适合的预览界面尺寸 - * - * @param parameters - * @param screenResolution - * @return - */ - private Point findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution) { - List rawSupportedSizes = parameters.getSupportedPreviewSizes(); - if (rawSupportedSizes == null) { - Log.w(TAG, "Device returned no supported preview sizes; using default"); - Camera.Size defaultSize = parameters.getPreviewSize(); - return new Point(defaultSize.width, defaultSize.height); - } - - // Sort by size, descending - List supportedPreviewSizes = new ArrayList(rawSupportedSizes); - Collections.sort(supportedPreviewSizes, new Comparator() { - @Override - public int compare(Camera.Size a, Camera.Size b) { - int aPixels = a.height * a.width; - int bPixels = b.height * b.width; - if (bPixels < aPixels) { - return -1; - } - if (bPixels > aPixels) { - return 1; - } - return 0; - } - }); - - if (Log.isLoggable(TAG, Log.INFO)) { - StringBuilder previewSizesString = new StringBuilder(); - for (Camera.Size supportedPreviewSize : supportedPreviewSizes) { - previewSizesString.append(supportedPreviewSize.width).append('x').append(supportedPreviewSize.height).append(' '); - } - Log.i(TAG, "Supported preview sizes: " + previewSizesString); - } - - double screenAspectRatio = (double) screenResolution.x / (double) screenResolution.y; - - // Remove sizes that are unsuitable - Iterator it = supportedPreviewSizes.iterator(); - while (it.hasNext()) { - Camera.Size supportedPreviewSize = it.next(); - int realWidth = supportedPreviewSize.width; - int realHeight = supportedPreviewSize.height; - if (realWidth * realHeight < MIN_PREVIEW_PIXELS) { - it.remove(); - continue; - } - - boolean isCandidatePortrait = realWidth < realHeight; - int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth; - int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight; - - double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight; - double distortion = Math.abs(aspectRatio - screenAspectRatio); - if (distortion > MAX_ASPECT_DISTORTION) { - it.remove(); - continue; - } - - if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) { - Point exactPoint = new Point(realWidth, realHeight); - Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint); - return exactPoint; - } - } - - // If no exact match, use largest preview size. This was not a great - // idea on older devices because - // of the additional computation needed. We're likely to get here on - // newer Android 4+ devices, where - // the CPU is much more powerful. - if (!supportedPreviewSizes.isEmpty()) { - Camera.Size largestPreview = supportedPreviewSizes.get(0); - Point largestSize = new Point(largestPreview.width, largestPreview.height); - Log.i(TAG, "Using largest suitable preview size: " + largestSize); - return largestSize; - } - - // If there is nothing at all suitable, return current preview size - Camera.Size defaultPreview = parameters.getPreviewSize(); - Point defaultSize = new Point(defaultPreview.width, defaultPreview.height); - Log.i(TAG, "No suitable preview sizes, using default: " + defaultSize); - - return defaultSize; - } + private static final String TAG = "CameraConfiguration"; + + private static final int MIN_PREVIEW_PIXELS = 480 * 320; + private static final double MAX_ASPECT_DISTORTION = 0.15; + + private final Context context; + + // 屏幕分辨率 + private Point screenResolution; + // 相机分辨率 + private Point cameraResolution; + + public CameraConfigurationManager(Context context) { + this.context = context; + } + + public void initFromCameraParameters(Camera camera) { + Camera.Parameters parameters = camera.getParameters(); + WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = manager.getDefaultDisplay(); + Point theScreenResolution = new Point(); + theScreenResolution = getDisplaySize(display); + + screenResolution = theScreenResolution; + Log.i(TAG, "Screen resolution: " + screenResolution); + + /** 因为换成了竖屏显示,所以不替换屏幕宽高得出的预览图是变形的 */ + Point screenResolutionForCamera = new Point(); + screenResolutionForCamera.x = screenResolution.x; + screenResolutionForCamera.y = screenResolution.y; + + if (screenResolution.x < screenResolution.y) { + screenResolutionForCamera.x = screenResolution.y; + screenResolutionForCamera.y = screenResolution.x; + } + + cameraResolution = findBestPreviewSizeValue(parameters, screenResolutionForCamera); + Log.i(TAG, "Camera resolution x: " + cameraResolution.x); + Log.i(TAG, "Camera resolution y: " + cameraResolution.y); + } + + @SuppressWarnings("deprecation") + @SuppressLint("NewApi") + private Point getDisplaySize(final Display display) { + final Point point = new Point(); + try { + display.getSize(point); + } catch (NoSuchMethodError ignore) { + point.x = display.getWidth(); + point.y = display.getHeight(); + } + return point; + } + + public void setDesiredCameraParameters(Camera camera, boolean safeMode) { + Camera.Parameters parameters = camera.getParameters(); + + if (parameters == null) { + Log.w(TAG, "Device error: no camera parameters are available. Proceeding without configuration."); + return; + } + + Log.i(TAG, "Initial camera parameters: " + parameters.flatten()); + + if (safeMode) { + Log.w(TAG, "In camera config safe mode -- most settings will not be honored"); + } + + parameters.setPreviewSize(cameraResolution.x, cameraResolution.y); + camera.setParameters(parameters); + + Camera.Parameters afterParameters = camera.getParameters(); + Camera.Size afterSize = afterParameters.getPreviewSize(); + if (afterSize != null && (cameraResolution.x != afterSize.width || cameraResolution.y != afterSize.height)) { + Log.w(TAG, "Camera said it supported preview size " + cameraResolution.x + 'x' + cameraResolution.y + ", but after setting it, preview size is " + afterSize.width + 'x' + afterSize.height); + cameraResolution.x = afterSize.width; + cameraResolution.y = afterSize.height; + } + + /** 设置相机预览为竖屏 */ + camera.setDisplayOrientation(90); + } + + public Point getCameraResolution() { + return cameraResolution; + } + + public Point getScreenResolution() { + return screenResolution; + } + + /** + * 从相机支持的分辨率中计算出最适合的预览界面尺寸 + * + * @param parameters + * @param screenResolution + * @return + */ + private Point findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution) { + List rawSupportedSizes = parameters.getSupportedPreviewSizes(); + if (rawSupportedSizes == null) { + Log.w(TAG, "Device returned no supported preview sizes; using default"); + Camera.Size defaultSize = parameters.getPreviewSize(); + return new Point(defaultSize.width, defaultSize.height); + } + + // Sort by size, descending + List supportedPreviewSizes = new ArrayList(rawSupportedSizes); + Collections.sort(supportedPreviewSizes, new Comparator() { + @Override + public int compare(Camera.Size a, Camera.Size b) { + int aPixels = a.height * a.width; + int bPixels = b.height * b.width; + if (bPixels < aPixels) { + return -1; + } + if (bPixels > aPixels) { + return 1; + } + return 0; + } + }); + + if (Log.isLoggable(TAG, Log.INFO)) { + StringBuilder previewSizesString = new StringBuilder(); + for (Camera.Size supportedPreviewSize : supportedPreviewSizes) { + previewSizesString.append(supportedPreviewSize.width).append('x').append(supportedPreviewSize.height).append(' '); + } + Log.i(TAG, "Supported preview sizes: " + previewSizesString); + } + + double screenAspectRatio = (double) screenResolution.x / (double) screenResolution.y; + + // Remove sizes that are unsuitable + Iterator it = supportedPreviewSizes.iterator(); + while (it.hasNext()) { + Camera.Size supportedPreviewSize = it.next(); + int realWidth = supportedPreviewSize.width; + int realHeight = supportedPreviewSize.height; + if (realWidth * realHeight < MIN_PREVIEW_PIXELS) { + it.remove(); + continue; + } + + boolean isCandidatePortrait = realWidth < realHeight; + int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth; + int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight; + + double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight; + double distortion = Math.abs(aspectRatio - screenAspectRatio); + if (distortion > MAX_ASPECT_DISTORTION) { + it.remove(); + continue; + } + + if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) { + Point exactPoint = new Point(realWidth, realHeight); + Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint); + return exactPoint; + } + } + + // If no exact match, use largest preview size. This was not a great + // idea on older devices because + // of the additional computation needed. We're likely to get here on + // newer Android 4+ devices, where + // the CPU is much more powerful. + if (!supportedPreviewSizes.isEmpty()) { + Camera.Size largestPreview = supportedPreviewSizes.get(0); + Point largestSize = new Point(largestPreview.width, largestPreview.height); + Log.i(TAG, "Using largest suitable preview size: " + largestSize); + return largestSize; + } + + // If there is nothing at all suitable, return current preview size + Camera.Size defaultPreview = parameters.getPreviewSize(); + Point defaultSize = new Point(defaultPreview.width, defaultPreview.height); + Log.i(TAG, "No suitable preview sizes, using default: " + defaultSize); + + return defaultSize; + } } diff --git a/app/src/main/java/com/dtr/zxing/camera/CameraManager.java b/app/src/main/java/com/dtr/zxing/camera/CameraManager.java index f7e4bbfdec6bb38bef1c0786fee6fbae7e493ea8..87ca68134f3d1b28658b6d58486dc345819fd22c 100644 --- a/app/src/main/java/com/dtr/zxing/camera/CameraManager.java +++ b/app/src/main/java/com/dtr/zxing/camera/CameraManager.java @@ -33,7 +33,7 @@ import java.io.IOException; * This object wraps the Camera service object and expects to be the only one * talking to it. The implementation encapsulates the steps needed to take * preview-sized images, which are used for both preview and decoding. - * + * * @author dswitkin@google.com (Daniel Switkin) */ public class CameraManager { @@ -57,176 +57,171 @@ public class CameraManager { private final PreviewCallback previewCallback; public CameraManager(Context context) { - this.context = context; - this.configManager = new CameraConfigurationManager(context); - previewCallback = new PreviewCallback(configManager); + this.context = context; + this.configManager = new CameraConfigurationManager(context); + previewCallback = new PreviewCallback(configManager); } /** * Opens the camera driver and initializes the hardware parameters. - * - * @param holder - * The surface object which the camera will draw preview frames - * into. - * @throws java.io.IOException - * Indicates the camera driver failed to open. + * + * @param holder The surface object which the camera will draw preview frames + * into. + * @throws java.io.IOException Indicates the camera driver failed to open. */ public synchronized void openDriver(SurfaceHolder holder) - throws IOException { - Camera theCamera = camera; - if (theCamera == null) { - - if (requestedCameraId >= 0) { - theCamera = OpenCameraInterface.open(requestedCameraId); - } else { - theCamera = OpenCameraInterface.open(); - } - - if (theCamera == null) { - throw new IOException(); - } - camera = theCamera; - } - theCamera.setPreviewDisplay(holder); - - if (!initialized) { - initialized = true; - configManager.initFromCameraParameters(theCamera); - } - - Camera.Parameters parameters = theCamera.getParameters(); - String parametersFlattened = parameters == null ? null : parameters - .flatten(); // Save - // these, - // temporarily - try { - configManager.setDesiredCameraParameters(theCamera, false); - } catch (RuntimeException re) { - // Driver failed - Log.w(TAG, - "Camera rejected parameters. Setting only minimal safe-mode parameters"); - Log.i(TAG, "Resetting to saved camera params: " - + parametersFlattened); - // Reset: - if (parametersFlattened != null) { - parameters = theCamera.getParameters(); - parameters.unflatten(parametersFlattened); - try { - theCamera.setParameters(parameters); - configManager.setDesiredCameraParameters(theCamera, true); - } catch (RuntimeException re2) { - // Well, darn. Give up - Log.w(TAG, - "Camera rejected even safe-mode parameters! No configuration"); - } - } - } + throws IOException { + Camera theCamera = camera; + if (theCamera == null) { + + if (requestedCameraId >= 0) { + theCamera = OpenCameraInterface.open(requestedCameraId); + } else { + theCamera = OpenCameraInterface.open(); + } + + if (theCamera == null) { + throw new IOException(); + } + camera = theCamera; + } + theCamera.setPreviewDisplay(holder); + + if (!initialized) { + initialized = true; + configManager.initFromCameraParameters(theCamera); + } + + Camera.Parameters parameters = theCamera.getParameters(); + String parametersFlattened = parameters == null ? null : parameters + .flatten(); // Save + // these, + // temporarily + try { + configManager.setDesiredCameraParameters(theCamera, false); + } catch (RuntimeException re) { + // Driver failed + Log.w(TAG, + "Camera rejected parameters. Setting only minimal safe-mode parameters"); + Log.i(TAG, "Resetting to saved camera params: " + + parametersFlattened); + // Reset: + if (parametersFlattened != null) { + parameters = theCamera.getParameters(); + parameters.unflatten(parametersFlattened); + try { + theCamera.setParameters(parameters); + configManager.setDesiredCameraParameters(theCamera, true); + } catch (RuntimeException re2) { + // Well, darn. Give up + Log.w(TAG, + "Camera rejected even safe-mode parameters! No configuration"); + } + } + } } public synchronized boolean isOpen() { - return camera != null; + return camera != null; } /** * Closes the camera driver if still in use. */ public synchronized void closeDriver() { - if (camera != null) { - camera.release(); - camera = null; - // Make sure to clear these each time we close the camera, so that - // any scanning rect - // requested by intent is forgotten. - } + if (camera != null) { + camera.release(); + camera = null; + // Make sure to clear these each time we close the camera, so that + // any scanning rect + // requested by intent is forgotten. + } } /** * Asks the camera hardware to begin drawing preview frames to the screen. */ public synchronized void startPreview() { - Camera theCamera = camera; - if (theCamera != null && !previewing) { - theCamera.startPreview(); - previewing = true; - autoFocusManager = new AutoFocusManager(context, camera); - } + Camera theCamera = camera; + if (theCamera != null && !previewing) { + theCamera.startPreview(); + previewing = true; + autoFocusManager = new AutoFocusManager(context, camera); + } } /** * Tells the camera to stop drawing preview frames. */ public synchronized void stopPreview() { - if (autoFocusManager != null) { - autoFocusManager.stop(); - autoFocusManager = null; - } - if (camera != null && previewing) { - camera.stopPreview(); - previewCallback.setHandler(null, 0); - previewing = false; - } + if (autoFocusManager != null) { + autoFocusManager.stop(); + autoFocusManager = null; + } + if (camera != null && previewing) { + camera.stopPreview(); + previewCallback.setHandler(null, 0); + previewing = false; + } } /** * A single preview frame will be returned to the handler supplied. The data * will arrive as byte[] in the message.obj field, with width and height * encoded as message.arg1 and message.arg2, respectively. - * - * @param handler - * The handler to send the message to. - * @param message - * The what field of the message to be sent. + * + * @param handler The handler to send the message to. + * @param message The what field of the message to be sent. */ public synchronized void requestPreviewFrame(Handler handler, int message) { - Camera theCamera = camera; - if (theCamera != null && previewing) { - previewCallback.setHandler(handler, message); - theCamera.setOneShotPreviewCallback(previewCallback); - } + Camera theCamera = camera; + if (theCamera != null && previewing) { + previewCallback.setHandler(handler, message); + theCamera.setOneShotPreviewCallback(previewCallback); + } } /** * Allows third party apps to specify the camera ID, rather than determine * it automatically based on available cameras and their orientation. - * - * @param cameraId - * camera ID of the camera to use. A negative value means - * "no preference". + * + * @param cameraId camera ID of the camera to use. A negative value means + * "no preference". */ public synchronized void setManualCameraId(int cameraId) { - requestedCameraId = cameraId; + requestedCameraId = cameraId; } /** * 获取相机分辨率 - * + * * @return */ public Point getCameraResolution() { - return configManager.getCameraResolution(); + return configManager.getCameraResolution(); } public Size getPreviewSize() { - if (null != camera) { - return camera.getParameters().getPreviewSize(); - } - return null; + if (null != camera) { + return camera.getParameters().getPreviewSize(); + } + return null; } public void openLight() { - if (camera != null) { - parameter = camera.getParameters(); - parameter.setFlashMode(Parameters.FLASH_MODE_TORCH); - camera.setParameters(parameter); - } + if (camera != null) { + parameter = camera.getParameters(); + parameter.setFlashMode(Parameters.FLASH_MODE_TORCH); + camera.setParameters(parameter); + } } public void offLight() { - if (camera != null) { - parameter = camera.getParameters(); - parameter.setFlashMode(Parameters.FLASH_MODE_OFF); - camera.setParameters(parameter); - } + if (camera != null) { + parameter = camera.getParameters(); + parameter.setFlashMode(Parameters.FLASH_MODE_OFF); + camera.setParameters(parameter); + } } } diff --git a/app/src/main/java/com/dtr/zxing/camera/PreviewCallback.java b/app/src/main/java/com/dtr/zxing/camera/PreviewCallback.java index c27c6928ffb430d6d238d5cd01cecf59e89e30dd..77634bd604bd431bd30ad721e173b0a581343e06 100644 --- a/app/src/main/java/com/dtr/zxing/camera/PreviewCallback.java +++ b/app/src/main/java/com/dtr/zxing/camera/PreviewCallback.java @@ -24,32 +24,32 @@ import android.util.Log; public class PreviewCallback implements Camera.PreviewCallback { - private static final String TAG = PreviewCallback.class.getSimpleName(); - - private final CameraConfigurationManager configManager; - private Handler previewHandler; - private int previewMessage; - - public PreviewCallback(CameraConfigurationManager configManager) { - this.configManager = configManager; - } - - public void setHandler(Handler previewHandler, int previewMessage) { - this.previewHandler = previewHandler; - this.previewMessage = previewMessage; - } - - @Override - public void onPreviewFrame(byte[] data, Camera camera) { - Point cameraResolution = configManager.getCameraResolution(); - Handler thePreviewHandler = previewHandler; - if (cameraResolution != null && thePreviewHandler != null) { - Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x, cameraResolution.y, data); - message.sendToTarget(); - previewHandler = null; - } else { - Log.d(TAG, "Got preview callback, but no handler or resolution available"); - } - } + private static final String TAG = PreviewCallback.class.getSimpleName(); + + private final CameraConfigurationManager configManager; + private Handler previewHandler; + private int previewMessage; + + public PreviewCallback(CameraConfigurationManager configManager) { + this.configManager = configManager; + } + + public void setHandler(Handler previewHandler, int previewMessage) { + this.previewHandler = previewHandler; + this.previewMessage = previewMessage; + } + + @Override + public void onPreviewFrame(byte[] data, Camera camera) { + Point cameraResolution = configManager.getCameraResolution(); + Handler thePreviewHandler = previewHandler; + if (cameraResolution != null && thePreviewHandler != null) { + Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x, cameraResolution.y, data); + message.sendToTarget(); + previewHandler = null; + } else { + Log.d(TAG, "Got preview callback, but no handler or resolution available"); + } + } } diff --git a/app/src/main/java/com/dtr/zxing/camera/open/OpenCameraInterface.java b/app/src/main/java/com/dtr/zxing/camera/open/OpenCameraInterface.java index f2866216845d16402ee707eb6eb134cd797f6442..c4f5afffbfa68ceac6ba63b78fb3fedf960d24c0 100644 --- a/app/src/main/java/com/dtr/zxing/camera/open/OpenCameraInterface.java +++ b/app/src/main/java/com/dtr/zxing/camera/open/OpenCameraInterface.java @@ -21,66 +21,65 @@ import android.util.Log; public class OpenCameraInterface { - private static final String TAG = OpenCameraInterface.class.getName(); + private static final String TAG = OpenCameraInterface.class.getName(); - /** - * Opens the requested camera with {@link android.hardware.Camera#open(int)}, if one exists. - * - * @param cameraId - * camera ID of the camera to use. A negative value means - * "no preference" - * @return handle to {@link android.hardware.Camera} that was opened - */ - public static Camera open(int cameraId) { + /** + * Opens the requested camera with {@link android.hardware.Camera#open(int)}, if one exists. + * + * @param cameraId camera ID of the camera to use. A negative value means + * "no preference" + * @return handle to {@link android.hardware.Camera} that was opened + */ + public static Camera open(int cameraId) { - int numCameras = Camera.getNumberOfCameras(); - if (numCameras == 0) { - Log.w(TAG, "No cameras!"); - return null; - } + int numCameras = Camera.getNumberOfCameras(); + if (numCameras == 0) { + Log.w(TAG, "No cameras!"); + return null; + } - boolean explicitRequest = cameraId >= 0; + boolean explicitRequest = cameraId >= 0; - if (!explicitRequest) { - // Select a camera if no explicit camera requested - int index = 0; - while (index < numCameras) { - Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); - Camera.getCameraInfo(index, cameraInfo); - if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { - break; - } - index++; - } + if (!explicitRequest) { + // Select a camera if no explicit camera requested + int index = 0; + while (index < numCameras) { + Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); + Camera.getCameraInfo(index, cameraInfo); + if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { + break; + } + index++; + } - cameraId = index; - } + cameraId = index; + } - Camera camera; - if (cameraId < numCameras) { - Log.i(TAG, "Opening camera #" + cameraId); - camera = Camera.open(cameraId); - } else { - if (explicitRequest) { - Log.w(TAG, "Requested camera does not exist: " + cameraId); - camera = null; - } else { - Log.i(TAG, "No camera facing back; returning camera #0"); - camera = Camera.open(0); - } - } + Camera camera; + if (cameraId < numCameras) { + Log.i(TAG, "Opening camera #" + cameraId); + camera = Camera.open(cameraId); + } else { + if (explicitRequest) { + Log.w(TAG, "Requested camera does not exist: " + cameraId); + camera = null; + } else { + Log.i(TAG, "No camera facing back; returning camera #0"); + camera = Camera.open(0); + } + } - return camera; - } + return camera; + } - /** - * Opens a rear-facing camera with {@link android.hardware.Camera#open(int)}, if one exists, - * or opens camera 0. - * - * @return handle to {@link android.hardware.Camera} that was opened - */ - public static Camera open() { - return open(-1); - } + /** + * Opens a rear-facing camera with {@link android.hardware.Camera#open(int)}, if one exists, + * or opens camera 0. + * + * @return handle to {@link android.hardware.Camera} that was opened + */ + public static Camera open() { + return open(-1); + } } diff --git a/app/src/main/java/com/dtr/zxing/decode/DecodeFormatManager.java b/app/src/main/java/com/dtr/zxing/decode/DecodeFormatManager.java index bdfe47088dd6fdf02ba8bdd2390871347e62df0c..dd0b135599c56f26f1468fd331f7ceab339f252d 100644 --- a/app/src/main/java/com/dtr/zxing/decode/DecodeFormatManager.java +++ b/app/src/main/java/com/dtr/zxing/decode/DecodeFormatManager.java @@ -24,28 +24,28 @@ import java.util.Set; public class DecodeFormatManager { - // 1D解码 - private static final Set PRODUCT_FORMATS; - private static final Set INDUSTRIAL_FORMATS; - private static final Set ONE_D_FORMATS; - - // 二维码解码 - private static final Set QR_CODE_FORMATS; - - static { - PRODUCT_FORMATS = EnumSet.of(BarcodeFormat.UPC_A, BarcodeFormat.UPC_E, BarcodeFormat.EAN_13, BarcodeFormat.EAN_8, BarcodeFormat.RSS_14, BarcodeFormat.RSS_EXPANDED); - INDUSTRIAL_FORMATS = EnumSet.of(BarcodeFormat.CODE_39, BarcodeFormat.CODE_93, BarcodeFormat.CODE_128, BarcodeFormat.ITF, BarcodeFormat.CODABAR); - ONE_D_FORMATS = EnumSet.copyOf(PRODUCT_FORMATS); - ONE_D_FORMATS.addAll(INDUSTRIAL_FORMATS); - - QR_CODE_FORMATS = EnumSet.of(BarcodeFormat.QR_CODE); - } - - public static Collection getQrCodeFormats() { - return QR_CODE_FORMATS; - } - - public static Collection getBarCodeFormats() { - return ONE_D_FORMATS; - } + // 1D解码 + private static final Set PRODUCT_FORMATS; + private static final Set INDUSTRIAL_FORMATS; + private static final Set ONE_D_FORMATS; + + // 二维码解码 + private static final Set QR_CODE_FORMATS; + + static { + PRODUCT_FORMATS = EnumSet.of(BarcodeFormat.UPC_A, BarcodeFormat.UPC_E, BarcodeFormat.EAN_13, BarcodeFormat.EAN_8, BarcodeFormat.RSS_14, BarcodeFormat.RSS_EXPANDED); + INDUSTRIAL_FORMATS = EnumSet.of(BarcodeFormat.CODE_39, BarcodeFormat.CODE_93, BarcodeFormat.CODE_128, BarcodeFormat.ITF, BarcodeFormat.CODABAR); + ONE_D_FORMATS = EnumSet.copyOf(PRODUCT_FORMATS); + ONE_D_FORMATS.addAll(INDUSTRIAL_FORMATS); + + QR_CODE_FORMATS = EnumSet.of(BarcodeFormat.QR_CODE); + } + + public static Collection getQrCodeFormats() { + return QR_CODE_FORMATS; + } + + public static Collection getBarCodeFormats() { + return ONE_D_FORMATS; + } } diff --git a/app/src/main/java/com/dtr/zxing/decode/DecodeHandler.java b/app/src/main/java/com/dtr/zxing/decode/DecodeHandler.java index 312b27340a10acf61895566240e846c0f57becc3..b01d050e8789e376a08fc7172385d071f23371e1 100644 --- a/app/src/main/java/com/dtr/zxing/decode/DecodeHandler.java +++ b/app/src/main/java/com/dtr/zxing/decode/DecodeHandler.java @@ -33,127 +33,121 @@ import com.google.zxing.ReaderException; import com.google.zxing.Result; import com.google.zxing.common.HybridBinarizer; +import net.oschina.app.R; + import java.io.ByteArrayOutputStream; import java.util.Map; -import net.oschina.app.R; - public class DecodeHandler extends Handler { - private final CaptureActivity activity; - private final MultiFormatReader multiFormatReader; - private boolean running = true; - - public DecodeHandler(CaptureActivity activity, Map hints) { - multiFormatReader = new MultiFormatReader(); - multiFormatReader.setHints(hints); - this.activity = activity; - } - - @Override - public void handleMessage(Message message) { - if (!running) { - return; - } - switch (message.what) { - case R.id.decode: - decode((byte[]) message.obj, message.arg1, message.arg2); - break; - case R.id.quit: - running = false; - Looper.myLooper().quit(); - break; - } - } - - /** - * Decode the data within the viewfinder rectangle, and time how long it - * took. For efficiency, reuse the same reader objects from one decode to - * the next. - * - * @param data - * The YUV preview frame. - * @param width - * The width of the preview frame. - * @param height - * The height of the preview frame. - */ - private void decode(byte[] data, int width, int height) { - Size size = activity.getCameraManager().getPreviewSize(); - - // 这里需要将获取的data翻转一下,因为相机默认拿的的横屏的数据 - byte[] rotatedData = new byte[data.length]; - for (int y = 0; y < size.height; y++) { - for (int x = 0; x < size.width; x++) - rotatedData[x * size.height + size.height - y - 1] = data[x + y * size.width]; - } - - // 宽高也要调整 - int tmp = size.width; - size.width = size.height; - size.height = tmp; - - Result rawResult = null; - PlanarYUVLuminanceSource source = buildLuminanceSource(rotatedData, size.width, size.height); - if (source != null) { - BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); - try { - rawResult = multiFormatReader.decodeWithState(bitmap); - } catch (ReaderException re) { - // continue - } finally { - multiFormatReader.reset(); - } - } - - Handler handler = activity.getHandler(); - if (rawResult != null) { - // Don't log the barcode contents for security. - if (handler != null) { - Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult); - Bundle bundle = new Bundle(); - bundleThumbnail(source, bundle); - message.setData(bundle); - message.sendToTarget(); - } - } else { - if (handler != null) { - Message message = Message.obtain(handler, R.id.decode_failed); - message.sendToTarget(); - } - } - - } - - private static void bundleThumbnail(PlanarYUVLuminanceSource source, Bundle bundle) { - int[] pixels = source.renderThumbnail(); - int width = source.getThumbnailWidth(); - int height = source.getThumbnailHeight(); - Bitmap bitmap = Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.JPEG, 50, out); - bundle.putByteArray(DecodeThread.BARCODE_BITMAP, out.toByteArray()); - } - - /** - * A factory method to build the appropriate LuminanceSource object based on - * the format of the preview buffers, as described by Camera.Parameters. - * - * @param data - * A preview frame. - * @param width - * The width of the image. - * @param height - * The height of the image. - * @return A PlanarYUVLuminanceSource instance. - */ - public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) { - Rect rect = activity.getCropRect(); - if (rect == null) { - return null; - } - // Go ahead and assume it's YUV rather than die. - return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, rect.width(), rect.height(), false); - } + private final CaptureActivity activity; + private final MultiFormatReader multiFormatReader; + private boolean running = true; + + public DecodeHandler(CaptureActivity activity, Map hints) { + multiFormatReader = new MultiFormatReader(); + multiFormatReader.setHints(hints); + this.activity = activity; + } + + @Override + public void handleMessage(Message message) { + if (!running) { + return; + } + switch (message.what) { + case R.id.decode: + decode((byte[]) message.obj, message.arg1, message.arg2); + break; + case R.id.quit: + running = false; + Looper.myLooper().quit(); + break; + } + } + + /** + * Decode the data within the viewfinder rectangle, and time how long it + * took. For efficiency, reuse the same reader objects from one decode to + * the next. + * + * @param data The YUV preview frame. + * @param width The width of the preview frame. + * @param height The height of the preview frame. + */ + private void decode(byte[] data, int width, int height) { + Size size = activity.getCameraManager().getPreviewSize(); + + // 这里需要将获取的data翻转一下,因为相机默认拿的的横屏的数据 + byte[] rotatedData = new byte[data.length]; + for (int y = 0; y < size.height; y++) { + for (int x = 0; x < size.width; x++) + rotatedData[x * size.height + size.height - y - 1] = data[x + y * size.width]; + } + + // 宽高也要调整 + int tmp = size.width; + size.width = size.height; + size.height = tmp; + + Result rawResult = null; + PlanarYUVLuminanceSource source = buildLuminanceSource(rotatedData, size.width, size.height); + if (source != null) { + BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); + try { + rawResult = multiFormatReader.decodeWithState(bitmap); + } catch (ReaderException re) { + // continue + } finally { + multiFormatReader.reset(); + } + } + + Handler handler = activity.getHandler(); + if (rawResult != null) { + // Don't log the barcode contents for security. + if (handler != null) { + Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult); + Bundle bundle = new Bundle(); + bundleThumbnail(source, bundle); + message.setData(bundle); + message.sendToTarget(); + } + } else { + if (handler != null) { + Message message = Message.obtain(handler, R.id.decode_failed); + message.sendToTarget(); + } + } + + } + + private static void bundleThumbnail(PlanarYUVLuminanceSource source, Bundle bundle) { + int[] pixels = source.renderThumbnail(); + int width = source.getThumbnailWidth(); + int height = source.getThumbnailHeight(); + Bitmap bitmap = Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.JPEG, 50, out); + bundle.putByteArray(DecodeThread.BARCODE_BITMAP, out.toByteArray()); + } + + /** + * A factory method to build the appropriate LuminanceSource object based on + * the format of the preview buffers, as described by Camera.Parameters. + * + * @param data A preview frame. + * @param width The width of the image. + * @param height The height of the image. + * @return A PlanarYUVLuminanceSource instance. + */ + public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) { + Rect rect = activity.getCropRect(); + if (rect == null) { + return null; + } + // Go ahead and assume it's YUV rather than die. + return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, rect.width(), rect.height(), false); + } } diff --git a/app/src/main/java/com/dtr/zxing/decode/DecodeThread.java b/app/src/main/java/com/dtr/zxing/decode/DecodeThread.java index 1dac54d38a65a3f2a3ea00f4e3f12376fa7d9387..f4efed2f7aa0537d2bbad203f29f6cee729531aa 100644 --- a/app/src/main/java/com/dtr/zxing/decode/DecodeThread.java +++ b/app/src/main/java/com/dtr/zxing/decode/DecodeThread.java @@ -32,69 +32,69 @@ import java.util.concurrent.CountDownLatch; /** * This thread does all the heavy lifting of decoding the images. - * + * * @author dswitkin@google.com (Daniel Switkin) */ public class DecodeThread extends Thread { - public static final String BARCODE_BITMAP = "barcode_bitmap"; - - public static final int BARCODE_MODE = 0X100; - public static final int QRCODE_MODE = 0X200; - public static final int ALL_MODE = 0X300; - - private final CaptureActivity activity; - private final Map hints; - private Handler handler; - private final CountDownLatch handlerInitLatch; - - public DecodeThread(CaptureActivity activity, int decodeMode) { - - this.activity = activity; - handlerInitLatch = new CountDownLatch(1); - - hints = new EnumMap(DecodeHintType.class); - - Collection decodeFormats = new ArrayList(); - decodeFormats.addAll(EnumSet.of(BarcodeFormat.AZTEC)); - decodeFormats.addAll(EnumSet.of(BarcodeFormat.PDF_417)); - - switch (decodeMode) { - case BARCODE_MODE: - decodeFormats.addAll(DecodeFormatManager.getBarCodeFormats()); - break; - - case QRCODE_MODE: - decodeFormats.addAll(DecodeFormatManager.getQrCodeFormats()); - break; - - case ALL_MODE: - decodeFormats.addAll(DecodeFormatManager.getBarCodeFormats()); - decodeFormats.addAll(DecodeFormatManager.getQrCodeFormats()); - break; - - default: - break; - } - - hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats); - } - - public Handler getHandler() { - try { - handlerInitLatch.await(); - } catch (InterruptedException ie) { - // continue? - } - return handler; - } - - @Override - public void run() { - Looper.prepare(); - handler = new DecodeHandler(activity, hints); - handlerInitLatch.countDown(); - Looper.loop(); - } + public static final String BARCODE_BITMAP = "barcode_bitmap"; + + public static final int BARCODE_MODE = 0X100; + public static final int QRCODE_MODE = 0X200; + public static final int ALL_MODE = 0X300; + + private final CaptureActivity activity; + private final Map hints; + private Handler handler; + private final CountDownLatch handlerInitLatch; + + public DecodeThread(CaptureActivity activity, int decodeMode) { + + this.activity = activity; + handlerInitLatch = new CountDownLatch(1); + + hints = new EnumMap(DecodeHintType.class); + + Collection decodeFormats = new ArrayList(); + decodeFormats.addAll(EnumSet.of(BarcodeFormat.AZTEC)); + decodeFormats.addAll(EnumSet.of(BarcodeFormat.PDF_417)); + + switch (decodeMode) { + case BARCODE_MODE: + decodeFormats.addAll(DecodeFormatManager.getBarCodeFormats()); + break; + + case QRCODE_MODE: + decodeFormats.addAll(DecodeFormatManager.getQrCodeFormats()); + break; + + case ALL_MODE: + decodeFormats.addAll(DecodeFormatManager.getBarCodeFormats()); + decodeFormats.addAll(DecodeFormatManager.getQrCodeFormats()); + break; + + default: + break; + } + + hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats); + } + + public Handler getHandler() { + try { + handlerInitLatch.await(); + } catch (InterruptedException ie) { + // continue? + } + return handler; + } + + @Override + public void run() { + Looper.prepare(); + handler = new DecodeHandler(activity, hints); + handlerInitLatch.countDown(); + Looper.loop(); + } } diff --git a/app/src/main/java/com/dtr/zxing/utils/BeepManager.java b/app/src/main/java/com/dtr/zxing/utils/BeepManager.java index fd7a2c2982d6ece420738840bdb1eefaae79e7be..345fd9bdcc52030f4aaaecf8a6b91c1953504ee5 100644 --- a/app/src/main/java/com/dtr/zxing/utils/BeepManager.java +++ b/app/src/main/java/com/dtr/zxing/utils/BeepManager.java @@ -25,119 +25,119 @@ import android.media.MediaPlayer; import android.os.Vibrator; import android.preference.PreferenceManager; import android.util.Log; + import com.dtr.zxing.activity.CaptureActivity; +import net.oschina.app.R; import java.io.Closeable; import java.io.IOException; -import net.oschina.app.R; - /** * Manages beeps and vibrations for {@link CaptureActivity}. */ public class BeepManager implements MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener, Closeable { - private static final String TAG = BeepManager.class.getSimpleName(); - - private static final float BEEP_VOLUME = 0.10f; - private static final long VIBRATE_DURATION = 200L; - - private final Activity activity; - private MediaPlayer mediaPlayer; - private boolean playBeep; - private boolean vibrate; - - public BeepManager(Activity activity) { - this.activity = activity; - this.mediaPlayer = null; - updatePrefs(); - } - - private synchronized void updatePrefs() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); - playBeep = shouldBeep(prefs, activity); - vibrate = true; - if (playBeep && mediaPlayer == null) { - // The volume on STREAM_SYSTEM is not adjustable, and users found it - // too loud, - // so we now play on the music stream. - activity.setVolumeControlStream(AudioManager.STREAM_MUSIC); - mediaPlayer = buildMediaPlayer(activity); - } - } - - public synchronized void playBeepSoundAndVibrate() { - if (playBeep && mediaPlayer != null) { - mediaPlayer.start(); - } - if (vibrate) { - Vibrator vibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE); - vibrator.vibrate(VIBRATE_DURATION); - } - } - - private static boolean shouldBeep(SharedPreferences prefs, Context activity) { - boolean shouldPlayBeep = true; - if (shouldPlayBeep) { - // See if sound settings overrides this - AudioManager audioService = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE); - if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) { - shouldPlayBeep = false; - } - } - return shouldPlayBeep; - } - - private MediaPlayer buildMediaPlayer(Context activity) { - MediaPlayer mediaPlayer = new MediaPlayer(); - mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - mediaPlayer.setOnCompletionListener(this); - mediaPlayer.setOnErrorListener(this); - try { - AssetFileDescriptor file = activity.getResources().openRawResourceFd(R.raw.qr_sacn); - try { - mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength()); - } finally { - file.close(); - } - mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME); - mediaPlayer.prepare(); - return mediaPlayer; - } catch (IOException ioe) { - Log.w(TAG, ioe); - mediaPlayer.release(); - return null; - } - } - - @Override - public void onCompletion(MediaPlayer mp) { - // When the beep has finished playing, rewind to queue up another one. - mp.seekTo(0); - } - - @Override - public synchronized boolean onError(MediaPlayer mp, int what, int extra) { - if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) { - // we are finished, so put up an appropriate error toast if required - // and finish - activity.finish(); - } else { - // possibly media player error, so release and recreate - mp.release(); - mediaPlayer = null; - updatePrefs(); - } - return true; - } - - @Override - public synchronized void close() { - if (mediaPlayer != null) { - mediaPlayer.release(); - mediaPlayer = null; - } - } + private static final String TAG = BeepManager.class.getSimpleName(); + + private static final float BEEP_VOLUME = 0.10f; + private static final long VIBRATE_DURATION = 200L; + + private final Activity activity; + private MediaPlayer mediaPlayer; + private boolean playBeep; + private boolean vibrate; + + public BeepManager(Activity activity) { + this.activity = activity; + this.mediaPlayer = null; + updatePrefs(); + } + + private synchronized void updatePrefs() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); + playBeep = shouldBeep(prefs, activity); + vibrate = true; + if (playBeep && mediaPlayer == null) { + // The volume on STREAM_SYSTEM is not adjustable, and users found it + // too loud, + // so we now play on the music stream. + activity.setVolumeControlStream(AudioManager.STREAM_MUSIC); + mediaPlayer = buildMediaPlayer(activity); + } + } + + public synchronized void playBeepSoundAndVibrate() { + if (playBeep && mediaPlayer != null) { + mediaPlayer.start(); + } + if (vibrate) { + Vibrator vibrator = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE); + vibrator.vibrate(VIBRATE_DURATION); + } + } + + private static boolean shouldBeep(SharedPreferences prefs, Context activity) { + boolean shouldPlayBeep = true; + if (shouldPlayBeep) { + // See if sound settings overrides this + AudioManager audioService = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE); + if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) { + shouldPlayBeep = false; + } + } + return shouldPlayBeep; + } + + private MediaPlayer buildMediaPlayer(Context activity) { + MediaPlayer mediaPlayer = new MediaPlayer(); + mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mediaPlayer.setOnCompletionListener(this); + mediaPlayer.setOnErrorListener(this); + try { + AssetFileDescriptor file = activity.getResources().openRawResourceFd(R.raw.qr_sacn); + try { + mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength()); + } finally { + file.close(); + } + mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME); + mediaPlayer.prepare(); + return mediaPlayer; + } catch (IOException ioe) { + Log.w(TAG, ioe); + mediaPlayer.release(); + return null; + } + } + + @Override + public void onCompletion(MediaPlayer mp) { + // When the beep has finished playing, rewind to queue up another one. + mp.seekTo(0); + } + + @Override + public synchronized boolean onError(MediaPlayer mp, int what, int extra) { + if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) { + // we are finished, so put up an appropriate error toast if required + // and finish + activity.finish(); + } else { + // possibly media player error, so release and recreate + mp.release(); + mediaPlayer = null; + updatePrefs(); + } + return true; + } + + @Override + public synchronized void close() { + if (mediaPlayer != null) { + mediaPlayer.release(); + mediaPlayer = null; + } + } } diff --git a/app/src/main/java/com/dtr/zxing/utils/CaptureActivityHandler.java b/app/src/main/java/com/dtr/zxing/utils/CaptureActivityHandler.java index d6272f44189a6a16bb3681b4767a2d2ac74a46cc..11110ab8cca5cd6a9b7acd3f5a5d2f59e6abc0c2 100644 --- a/app/src/main/java/com/dtr/zxing/utils/CaptureActivityHandler.java +++ b/app/src/main/java/com/dtr/zxing/utils/CaptureActivityHandler.java @@ -16,7 +16,6 @@ package com.dtr.zxing.utils; -import net.oschina.app.R; import android.app.Activity; import android.content.Intent; import android.os.Bundle; @@ -28,84 +27,86 @@ import com.dtr.zxing.camera.CameraManager; import com.dtr.zxing.decode.DecodeThread; import com.google.zxing.Result; +import net.oschina.app.R; + /** * This class handles all the messaging which comprises the state machine for * capture. - * + * * @author dswitkin@google.com (Daniel Switkin) */ public class CaptureActivityHandler extends Handler { - private final CaptureActivity activity; - private final DecodeThread decodeThread; - private final CameraManager cameraManager; - private State state; - - private enum State { - PREVIEW, SUCCESS, DONE - } - - public CaptureActivityHandler(CaptureActivity activity, CameraManager cameraManager, int decodeMode) { - this.activity = activity; - decodeThread = new DecodeThread(activity, decodeMode); - decodeThread.start(); - state = State.SUCCESS; - - // Start ourselves capturing previews and decoding. - this.cameraManager = cameraManager; - cameraManager.startPreview(); - restartPreviewAndDecode(); - } - - @Override - public void handleMessage(Message message) { - switch (message.what) { - case R.id.restart_preview: - restartPreviewAndDecode(); - break; - case R.id.decode_succeeded: - state = State.SUCCESS; - Bundle bundle = message.getData(); - - activity.handleDecode((Result) message.obj, bundle); - break; - case R.id.decode_failed: - // We're decoding as fast as possible, so when one decode fails, - // start another. - state = State.PREVIEW; - cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode); - break; - case R.id.return_scan_result: - activity.setResult(Activity.RESULT_OK, (Intent) message.obj); - activity.finish(); - break; - } - } - - public void quitSynchronously() { - state = State.DONE; - cameraManager.stopPreview(); - Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit); - quit.sendToTarget(); - try { - // Wait at most half a second; should be enough time, and onPause() - // will timeout quickly - decodeThread.join(500L); - } catch (InterruptedException e) { - // continue - } - - // Be absolutely sure we don't send any queued up messages - removeMessages(R.id.decode_succeeded); - removeMessages(R.id.decode_failed); - } - - private void restartPreviewAndDecode() { - if (state == State.SUCCESS) { - state = State.PREVIEW; - cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode); - } - } + private final CaptureActivity activity; + private final DecodeThread decodeThread; + private final CameraManager cameraManager; + private State state; + + private enum State { + PREVIEW, SUCCESS, DONE + } + + public CaptureActivityHandler(CaptureActivity activity, CameraManager cameraManager, int decodeMode) { + this.activity = activity; + decodeThread = new DecodeThread(activity, decodeMode); + decodeThread.start(); + state = State.SUCCESS; + + // Start ourselves capturing previews and decoding. + this.cameraManager = cameraManager; + cameraManager.startPreview(); + restartPreviewAndDecode(); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case R.id.restart_preview: + restartPreviewAndDecode(); + break; + case R.id.decode_succeeded: + state = State.SUCCESS; + Bundle bundle = message.getData(); + + activity.handleDecode((Result) message.obj, bundle); + break; + case R.id.decode_failed: + // We're decoding as fast as possible, so when one decode fails, + // start another. + state = State.PREVIEW; + cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode); + break; + case R.id.return_scan_result: + activity.setResult(Activity.RESULT_OK, (Intent) message.obj); + activity.finish(); + break; + } + } + + public void quitSynchronously() { + state = State.DONE; + cameraManager.stopPreview(); + Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit); + quit.sendToTarget(); + try { + // Wait at most half a second; should be enough time, and onPause() + // will timeout quickly + decodeThread.join(500L); + } catch (InterruptedException e) { + // continue + } + + // Be absolutely sure we don't send any queued up messages + removeMessages(R.id.decode_succeeded); + removeMessages(R.id.decode_failed); + } + + private void restartPreviewAndDecode() { + if (state == State.SUCCESS) { + state = State.PREVIEW; + cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode); + } + } } diff --git a/app/src/main/java/com/dtr/zxing/utils/InactivityTimer.java b/app/src/main/java/com/dtr/zxing/utils/InactivityTimer.java index 1230771b1e261f54ace4f24e5604619c91a201f0..dd119949b1280f52c81400762bf64c529ff842f1 100644 --- a/app/src/main/java/com/dtr/zxing/utils/InactivityTimer.java +++ b/app/src/main/java/com/dtr/zxing/utils/InactivityTimer.java @@ -33,92 +33,92 @@ import android.util.Log; */ public class InactivityTimer { - private static final String TAG = InactivityTimer.class.getSimpleName(); + private static final String TAG = InactivityTimer.class.getSimpleName(); - private static final long INACTIVITY_DELAY_MS = 5 * 60 * 1000L; + private static final long INACTIVITY_DELAY_MS = 5 * 60 * 1000L; - private Activity activity; - private BroadcastReceiver powerStatusReceiver; - private boolean registered; - private AsyncTask inactivityTask; + private Activity activity; + private BroadcastReceiver powerStatusReceiver; + private boolean registered; + private AsyncTask inactivityTask; - public InactivityTimer(Activity activity) { - this.activity = activity; - powerStatusReceiver = new PowerStatusReceiver(); - registered = false; - onActivity(); - } + public InactivityTimer(Activity activity) { + this.activity = activity; + powerStatusReceiver = new PowerStatusReceiver(); + registered = false; + onActivity(); + } - @SuppressLint("NewApi") - public synchronized void onActivity() { - cancel(); - inactivityTask = new InactivityAsyncTask(); - if (Build.VERSION.SDK_INT >= 11) { - inactivityTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - inactivityTask.execute(); - } - } + @SuppressLint("NewApi") + public synchronized void onActivity() { + cancel(); + inactivityTask = new InactivityAsyncTask(); + if (Build.VERSION.SDK_INT >= 11) { + inactivityTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + inactivityTask.execute(); + } + } - public synchronized void onPause() { - cancel(); - if (registered) { - activity.unregisterReceiver(powerStatusReceiver); - registered = false; - } else { - Log.w(TAG, "PowerStatusReceiver was never registered?"); - } - } + public synchronized void onPause() { + cancel(); + if (registered) { + activity.unregisterReceiver(powerStatusReceiver); + registered = false; + } else { + Log.w(TAG, "PowerStatusReceiver was never registered?"); + } + } - public synchronized void onResume() { - if (registered) { - Log.w(TAG, "PowerStatusReceiver was already registered?"); - } else { - activity.registerReceiver(powerStatusReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - registered = true; - } - onActivity(); - } + public synchronized void onResume() { + if (registered) { + Log.w(TAG, "PowerStatusReceiver was already registered?"); + } else { + activity.registerReceiver(powerStatusReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + registered = true; + } + onActivity(); + } - private synchronized void cancel() { - AsyncTask task = inactivityTask; - if (task != null) { - task.cancel(true); - inactivityTask = null; - } - } + private synchronized void cancel() { + AsyncTask task = inactivityTask; + if (task != null) { + task.cancel(true); + inactivityTask = null; + } + } - public void shutdown() { - cancel(); - } + public void shutdown() { + cancel(); + } - private class PowerStatusReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { - // 0 indicates that we're on battery - boolean onBatteryNow = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) <= 0; - if (onBatteryNow) { - InactivityTimer.this.onActivity(); - } else { - InactivityTimer.this.cancel(); - } - } - } - } + private class PowerStatusReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { + // 0 indicates that we're on battery + boolean onBatteryNow = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) <= 0; + if (onBatteryNow) { + InactivityTimer.this.onActivity(); + } else { + InactivityTimer.this.cancel(); + } + } + } + } - private class InactivityAsyncTask extends AsyncTask { - @Override - protected Object doInBackground(Object... objects) { - try { - Thread.sleep(INACTIVITY_DELAY_MS); - Log.i(TAG, "Finishing activity due to inactivity"); - activity.finish(); - } catch (InterruptedException e) { - // continue without killing - } - return null; - } - } + private class InactivityAsyncTask extends AsyncTask { + @Override + protected Object doInBackground(Object... objects) { + try { + Thread.sleep(INACTIVITY_DELAY_MS); + Log.i(TAG, "Finishing activity due to inactivity"); + activity.finish(); + } catch (InterruptedException e) { + // continue without killing + } + return null; + } + } } diff --git a/app/src/main/java/com/fourmob/datetimepicker/date/AccessibleDateAnimator.java b/app/src/main/java/com/fourmob/datetimepicker/date/AccessibleDateAnimator.java index d527c91ecfb8cf5d7cd156d10d2a4726e09d78c2..0f361959e4e7be97c6334d8c83ca17fb83f35544 100644 --- a/app/src/main/java/com/fourmob/datetimepicker/date/AccessibleDateAnimator.java +++ b/app/src/main/java/com/fourmob/datetimepicker/date/AccessibleDateAnimator.java @@ -8,9 +8,8 @@ import android.widget.ViewAnimator; /** * 摘取自https://github.com/flavienlaurent/datetimepicker - * + * * @author kymjs - * */ public class AccessibleDateAnimator extends ViewAnimator { private long mDateMillis; diff --git a/app/src/main/java/com/fourmob/datetimepicker/date/DatePickerController.java b/app/src/main/java/com/fourmob/datetimepicker/date/DatePickerController.java index a8c6400bc0787f9913ec8bd480bb7347445332a1..f51720701c271b693db0e30a4c1ea3560b1dca95 100644 --- a/app/src/main/java/com/fourmob/datetimepicker/date/DatePickerController.java +++ b/app/src/main/java/com/fourmob/datetimepicker/date/DatePickerController.java @@ -2,9 +2,8 @@ package com.fourmob.datetimepicker.date; /** * 摘取自https://github.com/flavienlaurent/datetimepicker - * + * * @author kymjs - * */ abstract interface DatePickerController { public abstract int getFirstDayOfWeek(); diff --git a/app/src/main/java/com/fourmob/datetimepicker/date/DatePickerDialog.java b/app/src/main/java/com/fourmob/datetimepicker/date/DatePickerDialog.java index 7d862a3db2c0823c94d19ee2dd79054294295c4d..d8c89aeaac59658edc783d1d00fef50d2fd9d910 100644 --- a/app/src/main/java/com/fourmob/datetimepicker/date/DatePickerDialog.java +++ b/app/src/main/java/com/fourmob/datetimepicker/date/DatePickerDialog.java @@ -1,13 +1,5 @@ package com.fourmob.datetimepicker.date; -import java.text.DateFormatSymbols; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Locale; - -import net.oschina.app.R; import android.app.Activity; import android.os.Bundle; import android.os.SystemClock; @@ -26,11 +18,19 @@ import android.widget.TextView; import com.nineoldandroids.animation.ObjectAnimator; +import net.oschina.app.R; + +import java.text.DateFormatSymbols; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; + /** * 摘取自https://github.com/flavienlaurent/datetimepicker - * + * * @author kymjs - * */ public class DatePickerDialog extends DialogFragment implements View.OnClickListener, DatePickerController { @@ -130,47 +130,47 @@ public class DatePickerDialog extends DialogFragment implements private void setCurrentView(int currentView, boolean forceRefresh) { long timeInMillis = mCalendar.getTimeInMillis(); switch (currentView) { - case MONTH_AND_DAY_VIEW: - ObjectAnimator monthDayAnim = Utils.getPulseAnimator( - mMonthAndDayView, 0.9F, 1.05F); - if (mDelayAnimation) { - monthDayAnim.setStartDelay(ANIMATION_DELAY); - mDelayAnimation = false; - } - mDayPickerView.onDateChanged(); - if (mCurrentView != currentView || forceRefresh) { - mMonthAndDayView.setSelected(true); - mYearView.setSelected(false); - mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW); - mCurrentView = currentView; - } - monthDayAnim.start(); - String monthDayDesc = DateUtils.formatDateTime(getActivity(), - timeInMillis, DateUtils.FORMAT_SHOW_DATE); - mAnimator.setContentDescription(mDayPickerDescription + ": " - + monthDayDesc); - Utils.tryAccessibilityAnnounce(mAnimator, mSelectDay); - break; - case YEAR_VIEW: - ObjectAnimator yearAnim = Utils.getPulseAnimator(mYearView, 0.85F, - 1.1F); - if (mDelayAnimation) { - yearAnim.setStartDelay(ANIMATION_DELAY); - mDelayAnimation = false; - } - mYearPickerView.onDateChanged(); - if (mCurrentView != currentView || forceRefresh) { - mMonthAndDayView.setSelected(false); - mYearView.setSelected(true); - mAnimator.setDisplayedChild(YEAR_VIEW); - mCurrentView = currentView; - } - yearAnim.start(); - String dayDesc = YEAR_FORMAT.format(timeInMillis); - mAnimator.setContentDescription(mYearPickerDescription + ": " - + dayDesc); - Utils.tryAccessibilityAnnounce(mAnimator, mSelectYear); - break; + case MONTH_AND_DAY_VIEW: + ObjectAnimator monthDayAnim = Utils.getPulseAnimator( + mMonthAndDayView, 0.9F, 1.05F); + if (mDelayAnimation) { + monthDayAnim.setStartDelay(ANIMATION_DELAY); + mDelayAnimation = false; + } + mDayPickerView.onDateChanged(); + if (mCurrentView != currentView || forceRefresh) { + mMonthAndDayView.setSelected(true); + mYearView.setSelected(false); + mAnimator.setDisplayedChild(MONTH_AND_DAY_VIEW); + mCurrentView = currentView; + } + monthDayAnim.start(); + String monthDayDesc = DateUtils.formatDateTime(getActivity(), + timeInMillis, DateUtils.FORMAT_SHOW_DATE); + mAnimator.setContentDescription(mDayPickerDescription + ": " + + monthDayDesc); + Utils.tryAccessibilityAnnounce(mAnimator, mSelectDay); + break; + case YEAR_VIEW: + ObjectAnimator yearAnim = Utils.getPulseAnimator(mYearView, 0.85F, + 1.1F); + if (mDelayAnimation) { + yearAnim.setStartDelay(ANIMATION_DELAY); + mDelayAnimation = false; + } + mYearPickerView.onDateChanged(); + if (mCurrentView != currentView || forceRefresh) { + mMonthAndDayView.setSelected(false); + mYearView.setSelected(true); + mAnimator.setDisplayedChild(YEAR_VIEW); + mCurrentView = currentView; + } + yearAnim.start(); + String dayDesc = YEAR_FORMAT.format(timeInMillis); + mAnimator.setContentDescription(mYearPickerDescription + ": " + + dayDesc); + Utils.tryAccessibilityAnnounce(mAnimator, mSelectYear); + break; } } @@ -245,7 +245,7 @@ public class DatePickerDialog extends DialogFragment implements } public void initialize(OnDateSetListener onDateSetListener, int year, - int month, int day, boolean vibrate) { + int month, int day, boolean vibrate) { if (year > MAX_YEAR) throw new IllegalArgumentException("year end must < " + MAX_YEAR); if (year < MIN_YEAR) @@ -266,6 +266,7 @@ public class DatePickerDialog extends DialogFragment implements setCurrentView(MONTH_AND_DAY_VIEW); } + @SuppressWarnings("WrongConstant") @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); @@ -284,12 +285,12 @@ public class DatePickerDialog extends DialogFragment implements @Override public View onCreateView(LayoutInflater layoutInflater, ViewGroup parent, - Bundle bundle) { + Bundle bundle) { getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE); View view = layoutInflater.inflate(R.layout.date_picker_dialog, null); - mDayOfWeekView = ((TextView) view.findViewById(R.id.date_picker_header)); + mDayOfWeekView = null; //这个布局没在View中怎么寻找的?((TextView) view.findViewById(R.id.date_picker_header)); mMonthAndDayView = ((LinearLayout) view .findViewById(R.id.date_picker_month_and_day)); mMonthAndDayView.setOnClickListener(this); @@ -472,6 +473,6 @@ public class DatePickerDialog extends DialogFragment implements public static abstract interface OnDateSetListener { public abstract void onDateSet(DatePickerDialog datePickerDialog, - int year, int month, int day); + int year, int month, int day); } } \ No newline at end of file diff --git a/app/src/main/java/com/fourmob/datetimepicker/date/DayPickerView.java b/app/src/main/java/com/fourmob/datetimepicker/date/DayPickerView.java index 495b7f20fbb3afd21008f5d9becf4c90ab7063a5..89f23caa228b77c53826c46898c9f3b39535b982 100644 --- a/app/src/main/java/com/fourmob/datetimepicker/date/DayPickerView.java +++ b/app/src/main/java/com/fourmob/datetimepicker/date/DayPickerView.java @@ -11,9 +11,8 @@ import android.widget.ListView; /** * 摘取自https://github.com/flavienlaurent/datetimepicker - * + * * @author kymjs - * */ public class DayPickerView extends ListView implements AbsListView.OnScrollListener, DatePickerDialog.OnDateChangedListener { @@ -46,7 +45,7 @@ public class DayPickerView extends ListView implements protected float mFriction = 1.0F; public DayPickerView(Context context, - DatePickerController datePickerController) { + DatePickerController datePickerController) { super(context); mController = datePickerController; mController.registerOnDateChangedListener(this); @@ -83,7 +82,7 @@ public class DayPickerView extends ListView implements } public boolean goTo(SimpleMonthAdapter.CalendarDay day, boolean animate, - boolean setSelected, boolean forceScroll) { + boolean setSelected, boolean forceScroll) { // Set the selected day if (setSelected) { mSelectedDay.set(day); @@ -162,7 +161,7 @@ public class DayPickerView extends ListView implements @Override public void onScroll(AbsListView view, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { + int visibleItemCount, int totalItemCount) { SimpleMonthView child = (SimpleMonthView) view.getChildAt(0); if (child == null) { return; @@ -229,11 +228,9 @@ public class DayPickerView extends ListView implements /** * Sets up the runnable with a short delay in case the scroll state * immediately changes again. - * - * @param view - * The list view that changed state - * @param scrollState - * The new state it changed to + * + * @param view The list view that changed state + * @param scrollState The new state it changed to */ public void doScrollStateChange(AbsListView view, int scrollState) { mHandler.removeCallbacks(this); diff --git a/app/src/main/java/com/fourmob/datetimepicker/date/SimpleMonthAdapter.java b/app/src/main/java/com/fourmob/datetimepicker/date/SimpleMonthAdapter.java index b46aec39f6f69e7354a851ac04a82e2400242860..86f4a0a4e8dceda3582713cfb25673e3fd77cc95 100644 --- a/app/src/main/java/com/fourmob/datetimepicker/date/SimpleMonthAdapter.java +++ b/app/src/main/java/com/fourmob/datetimepicker/date/SimpleMonthAdapter.java @@ -1,8 +1,5 @@ package com.fourmob.datetimepicker.date; -import java.util.Calendar; -import java.util.HashMap; - import android.content.Context; import android.view.View; import android.view.ViewGroup; @@ -10,11 +7,13 @@ import android.view.ViewGroup.LayoutParams; import android.widget.AbsListView; import android.widget.BaseAdapter; +import java.util.Calendar; +import java.util.HashMap; + /** * 摘取自https://github.com/flavienlaurent/datetimepicker - * + * * @author kymjs - * */ public class SimpleMonthAdapter extends BaseAdapter implements SimpleMonthView.OnDayClickListener { @@ -28,7 +27,7 @@ public class SimpleMonthAdapter extends BaseAdapter implements private CalendarDay mSelectedDay; public SimpleMonthAdapter(Context context, - DatePickerController datePickerController) { + DatePickerController datePickerController) { mContext = context; mController = datePickerController; init(); @@ -102,7 +101,7 @@ public class SimpleMonthAdapter extends BaseAdapter implements @Override public void onDayClick(SimpleMonthView simpleMonthView, - CalendarDay calendarDay) { + CalendarDay calendarDay) { if (calendarDay != null) { onDayTapped(calendarDay); } diff --git a/app/src/main/java/com/fourmob/datetimepicker/date/SimpleMonthView.java b/app/src/main/java/com/fourmob/datetimepicker/date/SimpleMonthView.java index 02cd291ae2dc166fd2d2643eae652f66208b3dfb..f150351142cd4d07e9b39de9bb788045500db73e 100644 --- a/app/src/main/java/com/fourmob/datetimepicker/date/SimpleMonthView.java +++ b/app/src/main/java/com/fourmob/datetimepicker/date/SimpleMonthView.java @@ -1,12 +1,5 @@ package com.fourmob.datetimepicker.date; -import java.security.InvalidParameterException; -import java.text.DateFormatSymbols; -import java.util.Calendar; -import java.util.HashMap; -import java.util.Locale; - -import net.oschina.app.R; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; @@ -19,11 +12,18 @@ import android.text.format.Time; import android.view.MotionEvent; import android.view.View; +import net.oschina.app.R; + +import java.security.InvalidParameterException; +import java.text.DateFormatSymbols; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Locale; + /** * 摘取自https://github.com/flavienlaurent/datetimepicker - * + * * @author kymjs - * */ public class SimpleMonthView extends View { @@ -354,6 +354,6 @@ public class SimpleMonthView extends View { public static abstract interface OnDayClickListener { public abstract void onDayClick(SimpleMonthView simpleMonthView, - SimpleMonthAdapter.CalendarDay calendarDay); + SimpleMonthAdapter.CalendarDay calendarDay); } } \ No newline at end of file diff --git a/app/src/main/java/com/fourmob/datetimepicker/date/TextViewWithCircularIndicator.java b/app/src/main/java/com/fourmob/datetimepicker/date/TextViewWithCircularIndicator.java index 5f355d201cbea137ea343fcc6d828e9b41e61b00..151f52cb8d04d0da79f3064465897e8862f2ad7a 100644 --- a/app/src/main/java/com/fourmob/datetimepicker/date/TextViewWithCircularIndicator.java +++ b/app/src/main/java/com/fourmob/datetimepicker/date/TextViewWithCircularIndicator.java @@ -1,6 +1,5 @@ package com.fourmob.datetimepicker.date; -import net.oschina.app.R; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; @@ -8,11 +7,12 @@ import android.graphics.Paint; import android.util.AttributeSet; import android.widget.TextView; +import net.oschina.app.R; + /** * 摘取自https://github.com/flavienlaurent/datetimepicker - * + * * @author kymjs - * */ public class TextViewWithCircularIndicator extends TextView { @@ -22,7 +22,7 @@ public class TextViewWithCircularIndicator extends TextView { private final String mItemIsSelectedText; public TextViewWithCircularIndicator(Context context, - AttributeSet attributeSet) { + AttributeSet attributeSet) { super(context, attributeSet); Resources res = context.getResources(); diff --git a/app/src/main/java/com/fourmob/datetimepicker/date/Utils.java b/app/src/main/java/com/fourmob/datetimepicker/date/Utils.java index d70d427c9953116c01d9f0f34c21a5713163ac47..b246af3759a573708af68315a7b1f90c4cf69a6e 100644 --- a/app/src/main/java/com/fourmob/datetimepicker/date/Utils.java +++ b/app/src/main/java/com/fourmob/datetimepicker/date/Utils.java @@ -1,7 +1,5 @@ package com.fourmob.datetimepicker.date; -import java.util.Calendar; - import android.annotation.SuppressLint; import android.os.Build; import android.view.View; @@ -11,11 +9,12 @@ import com.nineoldandroids.animation.Keyframe; import com.nineoldandroids.animation.ObjectAnimator; import com.nineoldandroids.animation.PropertyValuesHolder; +import java.util.Calendar; + /** * 摘取自https://github.com/flavienlaurent/datetimepicker - * + * * @author kymjs - * */ public class Utils { @@ -23,28 +22,28 @@ public class Utils { public static int getDaysInMonth(int month, int year) { switch (month) { - case Calendar.JANUARY: - case Calendar.MARCH: - case Calendar.MAY: - case Calendar.JULY: - case Calendar.AUGUST: - case Calendar.OCTOBER: - case Calendar.DECEMBER: - return 31; - case Calendar.APRIL: - case Calendar.JUNE: - case Calendar.SEPTEMBER: - case Calendar.NOVEMBER: - return 30; - case Calendar.FEBRUARY: - return (year % 4 == 0) ? 29 : 28; - default: - throw new IllegalArgumentException("Invalid Month"); + case Calendar.JANUARY: + case Calendar.MARCH: + case Calendar.MAY: + case Calendar.JULY: + case Calendar.AUGUST: + case Calendar.OCTOBER: + case Calendar.DECEMBER: + return 31; + case Calendar.APRIL: + case Calendar.JUNE: + case Calendar.SEPTEMBER: + case Calendar.NOVEMBER: + return 30; + case Calendar.FEBRUARY: + return (year % 4 == 0) ? 29 : 28; + default: + throw new IllegalArgumentException("Invalid Month"); } } public static ObjectAnimator getPulseAnimator(View labelToAnimate, - float decreaseRatio, float increaseRatio) { + float decreaseRatio, float increaseRatio) { Keyframe k0 = Keyframe.ofFloat(0f, 1f); Keyframe k1 = Keyframe.ofFloat(0.275f, decreaseRatio); Keyframe k2 = Keyframe.ofFloat(0.69f, increaseRatio); @@ -68,9 +67,8 @@ public class Utils { /** * Try to speak the specified text, for accessibility. Only available on JB * or later. - * - * @param text - * Text to announce. + * + * @param text Text to announce. */ @SuppressLint("NewApi") public static void tryAccessibilityAnnounce(View view, CharSequence text) { diff --git a/app/src/main/java/com/fourmob/datetimepicker/date/YearPickerView.java b/app/src/main/java/com/fourmob/datetimepicker/date/YearPickerView.java index a971fe4ec957fa206fd62de83fa038d67ac9247a..f0a1ad4350d4a6107c82bfd16e7b9868dc1031d5 100644 --- a/app/src/main/java/com/fourmob/datetimepicker/date/YearPickerView.java +++ b/app/src/main/java/com/fourmob/datetimepicker/date/YearPickerView.java @@ -1,9 +1,5 @@ package com.fourmob.datetimepicker.date; -import java.util.ArrayList; -import java.util.List; - -import net.oschina.app.R; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.StateListDrawable; @@ -14,11 +10,15 @@ import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; +import net.oschina.app.R; + +import java.util.ArrayList; +import java.util.List; + /** * 摘取自https://github.com/flavienlaurent/datetimepicker - * + * * @author kymjs - * */ public class YearPickerView extends ListView implements AdapterView.OnItemClickListener, DatePickerDialog.OnDateChangedListener { @@ -30,7 +30,7 @@ public class YearPickerView extends ListView implements private final int mViewSize; public YearPickerView(Context context, - DatePickerController datePickerController) { + DatePickerController datePickerController) { super(context); mController = datePickerController; mController.registerOnDateChangedListener(this); @@ -85,7 +85,7 @@ public class YearPickerView extends ListView implements @Override public void onItemClick(AdapterView parent, View view, int position, - long id) { + long id) { mController.tryVibrate(); TextViewWithCircularIndicator clickedView = (TextViewWithCircularIndicator) view; if (clickedView != null) { diff --git a/app/src/main/java/net/oschina/app/AppConfig.java b/app/src/main/java/net/oschina/app/AppConfig.java index f6cf28166feb3c74019c7742fda10bb67fce9623..618760f8a56399ccbe5f97b7ecd12235f087305e 100644 --- a/app/src/main/java/net/oschina/app/AppConfig.java +++ b/app/src/main/java/net/oschina/app/AppConfig.java @@ -1,9 +1,9 @@ package net.oschina.app; import android.content.Context; -import android.content.SharedPreferences; import android.os.Environment; -import android.preference.PreferenceManager; + +import net.oschina.common.utils.StreamUtil; import java.io.File; import java.io.FileInputStream; @@ -11,37 +11,16 @@ import java.io.FileOutputStream; import java.util.Properties; /** - * 应用程序配置类:用于保存用户相关信息及设置 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年9月25日 下午5:29:00 - * + * 应用程序配置类 + * 用于保存用户相关信息及设置 */ public class AppConfig { - private final static String APP_CONFIG = "config"; - - public final static String CONF_COOKIE = "cookie"; - - public final static String CONF_APP_UNIQUEID = "APP_UNIQUEID"; - public static final String KEY_LOAD_IMAGE = "KEY_LOAD_IMAGE"; - public static final String KEY_NOTIFICATION_ACCEPT = "KEY_NOTIFICATION_ACCEPT"; - public static final String KEY_NOTIFICATION_SOUND = "KEY_NOTIFICATION_SOUND"; - public static final String KEY_NOTIFICATION_VIBRATION = "KEY_NOTIFICATION_VIBRATION"; public static final String KEY_NOTIFICATION_DISABLE_WHEN_EXIT = "KEY_NOTIFICATION_DISABLE_WHEN_EXIT"; public static final String KEY_CHECK_UPDATE = "KEY_CHECK_UPDATE"; public static final String KEY_DOUBLE_CLICK_EXIT = "KEY_DOUBLE_CLICK_EXIT"; - public static final String KEY_TWEET_DRAFT = "KEY_TWEET_DRAFT"; - public static final String KEY_NOTE_DRAFT = "KEY_NOTE_DRAFT"; - - public static final String KEY_FRITST_START = "KEY_FRIST_START"; - - public static final String KEY_NIGHT_MODE_SWITCH="night_mode_switch"; - - public static final String APP_QQ_KEY = "100942993"; - // 默认存放图片的路径 public final static String DEFAULT_SAVE_IMAGE_PATH = Environment .getExternalStorageDirectory() @@ -67,13 +46,6 @@ public class AppConfig { return appConfig; } - /** - * 获取Preference设置 - */ - public static SharedPreferences getSharedPreferences(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context); - } - public String get(String key) { Properties props = get(); return (props != null) ? props.getProperty(key) : null; @@ -83,9 +55,6 @@ public class AppConfig { FileInputStream fis = null; Properties props = new Properties(); try { - // 读取files目录下的config - // fis = activity.openFileInput(APP_CONFIG); - // 读取app_config目录下的config File dirConf = mContext.getDir(APP_CONFIG, Context.MODE_PRIVATE); fis = new FileInputStream(dirConf.getPath() + File.separator @@ -93,11 +62,9 @@ public class AppConfig { props.load(fis); } catch (Exception e) { + e.printStackTrace(); } finally { - try { - fis.close(); - } catch (Exception e) { - } + StreamUtil.close(fis); } return props; } @@ -105,9 +72,6 @@ public class AppConfig { private void setProps(Properties p) { FileOutputStream fos = null; try { - // 把config建在files目录下 - // fos = activity.openFileOutput(APP_CONFIG, Context.MODE_PRIVATE); - // 把config建在(自定义)app_config的目录下 File dirConf = mContext.getDir(APP_CONFIG, Context.MODE_PRIVATE); File conf = new File(dirConf, APP_CONFIG); @@ -118,19 +82,10 @@ public class AppConfig { } catch (Exception e) { e.printStackTrace(); } finally { - try { - fos.close(); - } catch (Exception e) { - } + StreamUtil.close(fos); } } - public void set(Properties ps) { - Properties props = get(); - props.putAll(ps); - setProps(props); - } - public void set(String key, String value) { Properties props = get(); props.setProperty(key, value); @@ -142,5 +97,11 @@ public class AppConfig { for (String k : key) props.remove(k); setProps(props); + + //R.string.releaseUrl + //BuildConfig.API_SERVER_URL + + //BuildConfig.API_SERVER_URL } + } diff --git a/app/src/main/java/net/oschina/app/AppContext.java b/app/src/main/java/net/oschina/app/AppContext.java index f79b7c3a05224195f559f695d519bfd919257611..ce93f41d697b59a3f2faa8c356dbad359c7e4057 100644 --- a/app/src/main/java/net/oschina/app/AppContext.java +++ b/app/src/main/java/net/oschina/app/AppContext.java @@ -1,108 +1,38 @@ package net.oschina.app; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; - -import com.loopj.android.http.AsyncHttpClient; -import com.loopj.android.http.PersistentCookieStore; - -import net.oschina.app.api.ApiHttpClient; import net.oschina.app.base.BaseApplication; -import net.oschina.app.bean.Constants; -import net.oschina.app.bean.User; import net.oschina.app.cache.DataCleanManager; -import net.oschina.app.util.CyptoUtils; +import net.oschina.app.improve.detail.db.DBManager; import net.oschina.app.util.MethodsCompat; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.TLog; -import net.oschina.app.util.UIHelper; - -import org.kymjs.kjframe.Core; -import org.kymjs.kjframe.http.HttpConfig; -import org.kymjs.kjframe.utils.KJLoger; import java.util.Properties; -import java.util.UUID; -import static net.oschina.app.AppConfig.KEY_FRITST_START; import static net.oschina.app.AppConfig.KEY_LOAD_IMAGE; -import static net.oschina.app.AppConfig.KEY_NIGHT_MODE_SWITCH; -import static net.oschina.app.AppConfig.KEY_TWEET_DRAFT; /** - * 全局应用程序类:用于保存和调用全局应用配置及访问网络数据 - * - * @author 火蚁 (http://my.oschina.net/LittleDY) - * @version 1.0 - * @created 2014-04-22 + * 全局应用程序类 + * 用于保存和调用全局应用配置及访问网络数据 */ public class AppContext extends BaseApplication { - public static final int PAGE_SIZE = 20;// 默认分页大小 - private static AppContext instance; - private int loginUid; - - private boolean login; - @Override public void onCreate() { super.onCreate(); instance = this; - init(); - initLogin(); - -// Thread.setDefaultUncaughtExceptionHandler(AppException -// .getAppExceptionHandler(this)); - UIHelper.sendBroadcastForNotice(this); - } - - private void init() { - // 初始化网络请求 - AsyncHttpClient client = new AsyncHttpClient(); - PersistentCookieStore myCookieStore = new PersistentCookieStore(this); - client.setCookieStore(myCookieStore); - ApiHttpClient.setHttpClient(client); - ApiHttpClient.setCookie(ApiHttpClient.getCookie(this)); - - // Log控制器 - KJLoger.openDebutLog(true); - TLog.DEBUG = BuildConfig.DEBUG; - - // Bitmap缓存地址 - HttpConfig.CACHEPATH = "OSChina/imagecache"; - } - - private void initLogin() { - User user = getLoginUser(); - if (null != user && user.getId() > 0) { - login = true; - loginUid = user.getId(); - } else { - this.cleanLoginInfo(); - } + DBManager.init(this); } /** * 获得当前app运行的AppContext * - * @return + * @return AppContext */ public static AppContext getInstance() { return instance; } - public boolean containsProperty(String key) { - Properties props = getProperties(); - return props.containsKey(key); - } - - public void setProperties(Properties ps) { - AppConfig.getAppConfig(this).set(ps); - } - public Properties getProperties() { return AppConfig.getAppConfig(this).get(); } @@ -118,165 +48,18 @@ public class AppContext extends BaseApplication { * @return */ public String getProperty(String key) { - String res = AppConfig.getAppConfig(this).get(key); - return res; + return AppConfig.getAppConfig(this).get(key); } public void removeProperty(String... key) { AppConfig.getAppConfig(this).remove(key); } - /** - * 获取App唯一标识 - * - * @return - */ - public String getAppId() { - String uniqueID = getProperty(AppConfig.CONF_APP_UNIQUEID); - if (StringUtils.isEmpty(uniqueID)) { - uniqueID = UUID.randomUUID().toString(); - setProperty(AppConfig.CONF_APP_UNIQUEID, uniqueID); - } - return uniqueID; - } - - /** - * 获取App安装包信息 - * - * @return - */ - public PackageInfo getPackageInfo() { - PackageInfo info = null; - try { - info = getPackageManager().getPackageInfo(getPackageName(), 0); - } catch (NameNotFoundException e) { - e.printStackTrace(System.err); - } - if (info == null) - info = new PackageInfo(); - return info; - } - - /** - * 保存登录信息 - * - * @param user 用户信息 - */ - @SuppressWarnings("serial") - public void saveUserInfo(final User user) { - this.loginUid = user.getId(); - this.login = true; - setProperties(new Properties() { - { - setProperty("user.uid", String.valueOf(user.getId())); - setProperty("user.name", user.getName()); - setProperty("user.face", user.getPortrait());// 用户头像-文件名 - setProperty("user.account", user.getAccount()); - setProperty("user.pwd", - CyptoUtils.encode("oschinaApp", user.getPwd())); - setProperty("user.location", user.getLocation()); - setProperty("user.followers", - String.valueOf(user.getFollowers())); - setProperty("user.fans", String.valueOf(user.getFans())); - setProperty("user.score", String.valueOf(user.getScore())); - setProperty("user.favoritecount", - String.valueOf(user.getFavoritecount())); - setProperty("user.gender", String.valueOf(user.getGender())); - setProperty("user.isRememberMe", - String.valueOf(user.isRememberMe()));// 是否记住我的信息 - } - }); - } - - /** - * 更新用户信息 - * - * @param user - */ - @SuppressWarnings("serial") - public void updateUserInfo(final User user) { - setProperties(new Properties() { - { - setProperty("user.name", user.getName()); - setProperty("user.face", user.getPortrait());// 用户头像-文件名 - setProperty("user.followers", - String.valueOf(user.getFollowers())); - setProperty("user.fans", String.valueOf(user.getFans())); - setProperty("user.score", String.valueOf(user.getScore())); - setProperty("user.favoritecount", - String.valueOf(user.getFavoritecount())); - setProperty("user.gender", String.valueOf(user.getGender())); - } - }); - } - - /** - * 获得登录用户的信息 - * - * @return - */ - public User getLoginUser() { - User user = new User(); - user.setId(StringUtils.toInt(getProperty("user.uid"), 0)); - user.setName(getProperty("user.name")); - user.setPortrait(getProperty("user.face")); - user.setAccount(getProperty("user.account")); - user.setLocation(getProperty("user.location")); - user.setFollowers(StringUtils.toInt(getProperty("user.followers"), 0)); - user.setFans(StringUtils.toInt(getProperty("user.fans"), 0)); - user.setScore(StringUtils.toInt(getProperty("user.score"), 0)); - user.setFavoritecount(StringUtils.toInt( - getProperty("user.favoritecount"), 0)); - user.setRememberMe(StringUtils.toBool(getProperty("user.isRememberMe"))); - user.setGender(getProperty("user.gender")); - return user; - } - - /** - * 清除登录信息 - */ - public void cleanLoginInfo() { - this.loginUid = 0; - this.login = false; - removeProperty("user.uid", "user.name", "user.face", "user.location", - "user.followers", "user.fans", "user.score", - "user.isRememberMe", "user.gender", "user.favoritecount"); - } - - public int getLoginUid() { - return loginUid; - } - - public boolean isLogin() { - return login; - } - - /** - * 用户注销 - */ - public void Logout() { - cleanLoginInfo(); - ApiHttpClient.cleanCookie(); - this.cleanCookie(); - this.login = false; - this.loginUid = 0; - - Intent intent = new Intent(Constants.INTENT_ACTION_LOGOUT); - sendBroadcast(intent); - } - - /** - * 清除保存的缓存 - */ - public void cleanCookie() { - removeProperty(AppConfig.CONF_COOKIE); - } - /** * 清除app缓存 */ public void clearAppCache() { - DataCleanManager.cleanDatabases(this); + //DataCleanManager.cleanDatabases(this); // 清除数据缓存 DataCleanManager.cleanInternalCache(this); // 2.2版本才有将应用缓存转移到sd卡的功能 @@ -284,6 +67,25 @@ public class AppContext extends BaseApplication { DataCleanManager.cleanCustomCache(MethodsCompat .getExternalCacheDir(this)); } + + /* + Run.onUiSync(new Action() { + @Override + public void call() { + // Glide 清理内存必须在主线程 + Glide.get(OSCApplication.getInstance()).clearMemory(); + } + }); + + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + // Glide 清理磁盘必须在子线程 + Glide.get(OSCApplication.getInstance()).clearDiskCache(); + } + }); + */ + // 清除编辑器保存的临时内容 Properties props = getProperties(); for (Object key : props.keySet()) { @@ -291,7 +93,6 @@ public class AppContext extends BaseApplication { if (_key.startsWith("temp")) removeProperty(_key); } - Core.getKJBitmap().cleanCache(); } public static void setLoadImage(boolean flag) { @@ -308,40 +109,4 @@ public class AppContext extends BaseApplication { int currentVersion = android.os.Build.VERSION.SDK_INT; return currentVersion >= VersionCode; } - - public static String getTweetDraft() { - return getPreferences().getString( - KEY_TWEET_DRAFT + getInstance().getLoginUid(), ""); - } - - public static void setTweetDraft(String draft) { - set(KEY_TWEET_DRAFT + getInstance().getLoginUid(), draft); - } - - public static String getNoteDraft() { - return getPreferences().getString( - AppConfig.KEY_NOTE_DRAFT + getInstance().getLoginUid(), ""); - } - - public static void setNoteDraft(String draft) { - set(AppConfig.KEY_NOTE_DRAFT + getInstance().getLoginUid(), draft); - } - - public static boolean isFristStart() { - return getPreferences().getBoolean(KEY_FRITST_START, true); - } - - public static void setFristStart(boolean frist) { - set(KEY_FRITST_START, frist); - } - - //夜间模式 - public static boolean getNightModeSwitch() { - return getPreferences().getBoolean(KEY_NIGHT_MODE_SWITCH, false); - } - - // 设置夜间模式 - public static void setNightModeSwitch(boolean on) { - set(KEY_NIGHT_MODE_SWITCH, on); - } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/AppCrashHandler.java b/app/src/main/java/net/oschina/app/AppCrashHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..d577c5eb7a516feb404c5466cf54c6f587ce75dd --- /dev/null +++ b/app/src/main/java/net/oschina/app/AppCrashHandler.java @@ -0,0 +1,77 @@ +package net.oschina.app; + +import android.content.Context; +import android.os.Looper; +import android.widget.Toast; + +import net.oschina.app.improve.main.ErrorActivity; + +/** + * Created by JuQiu + * on 2016/9/13. + */ + +public class AppCrashHandler implements Thread.UncaughtExceptionHandler { + public static final String TAG = "CrashHandler"; + + private Thread.UncaughtExceptionHandler mDefaultHandler; + private static AppCrashHandler INSTANCE = new AppCrashHandler(); + private Context mContext; + + private AppCrashHandler() { + } + + public static AppCrashHandler getInstance() { + return INSTANCE; + } + + public void init(Context context) { + mContext = context; + + Thread.UncaughtExceptionHandler exceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); + if (exceptionHandler == this) + return; + mDefaultHandler = exceptionHandler; + Thread.setDefaultUncaughtExceptionHandler(this); + } + + @Override + public void uncaughtException(Thread thread, Throwable ex) { + //ErrorActivity.show(mContext, ex.getMessage()); + if (mDefaultHandler != null && (BuildConfig.DEBUG || (!handleException(ex)))) { + mDefaultHandler.uncaughtException(thread, ex); + } else { + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(1); + } + } + + private boolean handleException(Throwable ex) { + if (ex == null) { + return false; + } + ex.printStackTrace(); + + + new Thread() { + @Override + public void run() { + Looper.prepare(); + Toast.makeText(mContext, "OSC异常;正准备重启!!", Toast.LENGTH_LONG).show(); + Looper.loop(); + } + }.start(); + + AppContext.getInstance().clearAppCache(); + + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + + return true; + } +} diff --git a/app/src/main/java/net/oschina/app/AppException.java b/app/src/main/java/net/oschina/app/AppException.java index e4d9caa700661a6714ecc727a0be04ed5c3d3734..472313c04c56215a72c1fd82977d3d47d608429c 100644 --- a/app/src/main/java/net/oschina/app/AppException.java +++ b/app/src/main/java/net/oschina/app/AppException.java @@ -1,40 +1,21 @@ package net.oschina.app; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; import java.io.IOException; -import java.io.PrintWriter; -import java.lang.Thread.UncaughtExceptionHandler; import java.net.ConnectException; -import java.net.SocketException; import java.net.UnknownHostException; -import net.oschina.app.util.UIHelper; - -import org.kymjs.kjframe.utils.FileUtils; -import org.kymjs.kjframe.utils.SystemTool; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Build; -import android.os.Looper; -import cz.msebera.android.httpclient.HttpException; - /** * 应用程序异常:用于捕获异常和提示错误信息 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @author kymjs (kymjs123@gmali.com) * @created 2014年9月25日 下午5:34:05 - * */ @SuppressWarnings("serial") -public class AppException extends Exception implements UncaughtExceptionHandler { - - /** 定义异常类型 */ +public class AppException extends Exception { + /** + * 定义异常类型 + */ public final static byte TYPE_NETWORK = 0x01; public final static byte TYPE_SOCKET = 0x02; public final static byte TYPE_HTTP_CODE = 0x03; @@ -49,13 +30,6 @@ public class AppException extends Exception implements UncaughtExceptionHandler // 异常的状态码,这里一般是网络请求的状态码 private int code; - /** 系统默认的UncaughtException处理类 */ - private AppContext mContext; - - private AppException(Context context) { - this.mContext = (AppContext) context; - } - private AppException(byte type, int code, Exception excp) { super(excp); this.type = type; @@ -109,125 +83,7 @@ public class AppException extends Exception implements UncaughtExceptionHandler return new AppException(TYPE_JSON, 0, e); } - // 网络请求异常 - public static AppException network(Exception e) { - if (e instanceof UnknownHostException || e instanceof ConnectException) { - return new AppException(TYPE_NETWORK, 0, e); - } else if (e instanceof HttpException) { - return http(e); - } else if (e instanceof SocketException) { - return socket(e); - } - return http(e); - } - public static AppException run(Exception e) { return new AppException(TYPE_RUN, 0, e); } - - /** - * 获取APP异常崩溃处理对象 - * - * @param context - * @return - */ - public static AppException getAppExceptionHandler(Context context) { - return new AppException(context.getApplicationContext()); - } - - @Override - public void uncaughtException(Thread thread, Throwable ex) { - if (!handleException(ex)) { - System.exit(0); - } - } - - /** - * 自定义异常处理:收集错误信息&发送错误报告 - * - * @param ex - * @return true:处理了该异常信息;否则返回false - */ - private boolean handleException(final Throwable ex) { - if (ex == null || mContext == null) { - return false; - } - boolean success = true; - try { - success = saveToSDCard(ex); - } catch (Exception e) { - } finally { - if (!success) { - return false; - } else { - final Context context = AppManager.getAppManager() - .currentActivity(); - // 显示异常信息&发送报告 - new Thread() { - @Override - public void run() { - Looper.prepare(); - // 拿到未捕获的异常, - UIHelper.sendAppCrashReport(context); - Looper.loop(); - } - }.start(); - } - } - return true; - } - - private boolean saveToSDCard(Throwable ex) throws Exception { - boolean append = false; - File file = FileUtils.getSaveFile("OSChina", "OSCLog.log"); - if (System.currentTimeMillis() - file.lastModified() > 5000) { - append = true; - } - PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter( - file, append))); - // 导出发生异常的时间 - pw.println(SystemTool.getDataTime("yyyy-MM-dd-HH-mm-ss")); - // 导出手机信息 - dumpPhoneInfo(pw); - pw.println(); - // 导出异常的调用栈信息 - ex.printStackTrace(pw); - pw.println(); - pw.close(); - return append; - } - - private void dumpPhoneInfo(PrintWriter pw) throws NameNotFoundException { - // 应用的版本名称和版本号 - PackageManager pm = mContext.getPackageManager(); - PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), - PackageManager.GET_ACTIVITIES); - pw.print("App Version: "); - pw.print(pi.versionName); - pw.print('_'); - pw.println(pi.versionCode); - pw.println(); - - // android版本号 - pw.print("OS Version: "); - pw.print(Build.VERSION.RELEASE); - pw.print("_"); - pw.println(Build.VERSION.SDK_INT); - pw.println(); - - // 手机制造商 - pw.print("Vendor: "); - pw.println(Build.MANUFACTURER); - pw.println(); - - // 手机型号 - pw.print("Model: "); - pw.println(Build.MODEL); - pw.println(); - - // cpu架构 - pw.print("CPU ABI: "); - pw.println(Build.CPU_ABI); - pw.println(); - } } diff --git a/app/src/main/java/net/oschina/app/AppManager.java b/app/src/main/java/net/oschina/app/AppManager.java deleted file mode 100644 index fde4ac328af0ca1c69b8ccf12811e0e4c8a1ddba..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/AppManager.java +++ /dev/null @@ -1,127 +0,0 @@ -package net.oschina.app; - -import java.util.Stack; - -import android.app.Activity; -import android.content.Context; - -/** - * activity堆栈式管理 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年10月30日 下午6:22:05 - * - */ -public class AppManager { - - private static Stack activityStack; - private static AppManager instance; - - private AppManager() {} - - /** - * 单一实例 - */ - public static AppManager getAppManager() { - if (instance == null) { - instance = new AppManager(); - } - return instance; - } - - /** - * 添加Activity到堆栈 - */ - public void addActivity(Activity activity) { - if (activityStack == null) { - activityStack = new Stack(); - } - activityStack.add(activity); - } - - /** - * 获取当前Activity(堆栈中最后一个压入的) - */ - public Activity currentActivity() { - Activity activity = activityStack.lastElement(); - return activity; - } - - /** - * 结束当前Activity(堆栈中最后一个压入的) - */ - public void finishActivity() { - Activity activity = activityStack.lastElement(); - finishActivity(activity); - } - - /** - * 结束指定的Activity - */ - public void finishActivity(Activity activity) { - if (activity != null && !activity.isFinishing()) { - activityStack.remove(activity); - activity.finish(); - activity = null; - } - } - - /** - * 结束指定类名的Activity - */ - public void finishActivity(Class cls) { - for (Activity activity : activityStack) { - if (activity.getClass().equals(cls)) { - finishActivity(activity); - break; - } - } - } - - /** - * 结束所有Activity - */ - public void finishAllActivity() { - for (int i = 0, size = activityStack.size(); i < size; i++) { - if (null != activityStack.get(i)) { - //finishActivity方法中的activity.isFinishing()方法会导致某些activity无法销毁 - //貌似跳转的时候最后一个activity 是finishing状态,所以没有执行 - //内部实现不是很清楚,但是实测结果如此,使用下面代码则没有问题 - // find by TopJohn - //finishActivity(activityStack.get(i)); - - activityStack.get(i).finish(); - //break; - } - } - activityStack.clear(); - } - - /** - * 获取指定的Activity - * - * @author kymjs - */ - public static Activity getActivity(Class cls) { - if (activityStack != null) - for (Activity activity : activityStack) { - if (activity.getClass().equals(cls)) { - return activity; - } - } - return null; - } - - /** - * 退出应用程序 - */ - public void AppExit(Context context) { - try { - finishAllActivity(); - // 杀死该应用进程 - android.os.Process.killProcess(android.os.Process.myPid()); - System.exit(0); - } catch (Exception e) { - } - } -} diff --git a/app/src/main/java/net/oschina/app/AppStart.java b/app/src/main/java/net/oschina/app/AppStart.java deleted file mode 100644 index 03161d07331d60e1a43f6584e54f655181e07aed..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/AppStart.java +++ /dev/null @@ -1,94 +0,0 @@ -package net.oschina.app; - -import java.io.File; - -import net.oschina.app.ui.MainActivity; -import net.oschina.app.util.TDevice; - -import org.kymjs.kjframe.http.KJAsyncTask; -import org.kymjs.kjframe.utils.FileUtils; -import org.kymjs.kjframe.utils.PreferenceHelper; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; - -/** - * 应用启动界面 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年12月22日 上午11:51:56 - * - */ -public class AppStart extends Activity { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // 防止第三方跳转时出现双实例 - Activity aty = AppManager.getActivity(MainActivity.class); - if (aty != null && !aty.isFinishing()) { - finish(); - } - // SystemTool.gc(this); //针对性能好的手机使用,加快应用相应速度 - - final View view = View.inflate(this, R.layout.app_start, null); - setContentView(view); - // 渐变展示启动屏 - AlphaAnimation aa = new AlphaAnimation(0.5f, 1.0f); - aa.setDuration(800); - view.startAnimation(aa); - aa.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationEnd(Animation arg0) { - redirectTo(); - } - - @Override - public void onAnimationRepeat(Animation animation) {} - - @Override - public void onAnimationStart(Animation animation) {} - }); - } - - @Override - protected void onResume() { - super.onResume(); - int cacheVersion = PreferenceHelper.readInt(this, "first_install", - "first_install", -1); - int currentVersion = TDevice.getVersionCode(); - if (cacheVersion < currentVersion) { - PreferenceHelper.write(this, "first_install", "first_install", - currentVersion); - cleanImageCache(); - } - } - - private void cleanImageCache() { - final File folder = FileUtils.getSaveFolder("OSChina/imagecache"); - KJAsyncTask.execute(new Runnable() { - @Override - public void run() { - for (File file : folder.listFiles()) { - file.delete(); - } - } - }); - } - - /** - * 跳转到... - */ - private void redirectTo() { - Intent uploadLog = new Intent(this, LogUploadService.class); - startService(uploadLog); - Intent intent = new Intent(this, MainActivity.class); - startActivity(intent); - finish(); - } -} diff --git a/app/src/main/java/net/oschina/app/LaunchActivity.java b/app/src/main/java/net/oschina/app/LaunchActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..6d9195c27aa35bab4c601543bd0c98bbe5bd3d31 --- /dev/null +++ b/app/src/main/java/net/oschina/app/LaunchActivity.java @@ -0,0 +1,77 @@ +package net.oschina.app; + +import android.text.TextUtils; + +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.main.MainActivity; +import net.oschina.app.improve.main.introduce.IntroduceActivity; +import net.oschina.app.improve.main.tabs.DynamicTabFragment; +import net.oschina.app.improve.main.update.OSCSharedPreference; + +/** + * 应用启动界面 + */ +public class LaunchActivity extends BaseActivity { + @Override + protected int getContentView() { + return R.layout.app_start; + } + + @Override + protected void initData() { + super.initData(); + // 在这里我们检测是否是新版本安装,如果是则进行老版本数据迁移工作 + // 该工作可能消耗大量时间所以放在自线程中执行 + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + doMerge(); + } + }); + } + + private void doMerge() { + // 判断是否是新版本 + if (Setting.checkIsNewVersion(this)) { + // Cookie迁移 + String cookie = OSCApplication.getInstance().getProperty("cookie"); + if (!TextUtils.isEmpty(cookie)) { + OSCApplication.getInstance().removeProperty("cookie"); + User user = AccountHelper.getUser(); + user.setCookie(cookie); + AccountHelper.updateUserCache(user); + OSCApplication.reInit(); + } + } + + // 栏目相关数据合并操作 + DynamicTabFragment.initTabPickerManager(); + + // Delay... + try { + Thread.sleep(800); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // 完成后进行跳转操作 + redirectTo(); + } + + private void redirectTo() { + if (OSCSharedPreference.getInstance().isFirstInstall()) { + IntroduceActivity.show(this); + finish(); + } else { + MainActivity.show(this); + } + } + + @Override + public void onBackPressed() { + //super.onBackPressed(); + } +} diff --git a/app/src/main/java/net/oschina/app/LogUploadService.java b/app/src/main/java/net/oschina/app/LogUploadService.java deleted file mode 100644 index b0dd7ef6c5aa355e7b28e819118da0fd6f5f732f..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/LogUploadService.java +++ /dev/null @@ -1,55 +0,0 @@ -package net.oschina.app; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; - -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.util.StringUtils; - -import cz.msebera.android.httpclient.Header; -import org.kymjs.kjframe.utils.FileUtils; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; - -import com.loopj.android.http.AsyncHttpResponseHandler; - -public class LogUploadService extends Service { - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - final File log = FileUtils.getSaveFile("OSChina", "OSCLog.log"); - String data = null; - try { - FileInputStream inputStream = new FileInputStream(log); - data = StringUtils.toConvertString(inputStream); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - if (!StringUtils.isEmpty(data)) { - OSChinaApi.uploadLog(data, new AsyncHttpResponseHandler() { - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - log.delete(); - LogUploadService.this.stopSelf(); - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) { - LogUploadService.this.stopSelf(); - } - }); - } else { - LogUploadService.this.stopSelf(); - } - return super.onStartCommand(intent, flags, startId); - } -} diff --git a/app/src/main/java/net/oschina/app/OSCApplication.java b/app/src/main/java/net/oschina/app/OSCApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..412220a66c37f45f00a739ec34c5fd847ee04564 --- /dev/null +++ b/app/src/main/java/net/oschina/app/OSCApplication.java @@ -0,0 +1,135 @@ +package net.oschina.app; + +import android.content.Context; +import android.provider.Settings; +import android.support.multidex.MultiDex; +import android.text.TextUtils; + + +import com.baidu.mapapi.SDKInitializer; + +import net.oschina.app.api.ApiHttpClient; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.detail.db.DBManager; +import net.oschina.app.improve.detail.v2.DetailCache; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.improve.utils.MD5; +import net.oschina.common.helper.ReadStateHelper; + +import java.util.UUID; + +/** + * Created by qiujuer + * on 2016/10/27. + */ +public class OSCApplication extends AppContext { + private static final String CONFIG_READ_STATE_PRE = "CONFIG_READ_STATE_PRE_"; + + @Override + public void onCreate() { + super.onCreate(); + // 初始化操作 + DetailCache.init(getApplicationContext()); + init(); + } + + public static void reInit() { + ((OSCApplication) OSCApplication.getInstance()).init(); + } + + private void init() { + BaseActivity.IS_ACTIVE = true; + OSCSharedPreference.init(this, "osc_update_sp"); + if (TextUtils.isEmpty(OSCSharedPreference.getInstance().getDeviceUUID())) { + String androidId = Settings.System.getString(getContentResolver(), Settings.System.ANDROID_ID); + if (TextUtils.isEmpty(androidId)) { + androidId = UUID.randomUUID().toString().replaceAll("-", ""); + } + OSCSharedPreference.getInstance().putDeviceUUID(MD5.get32MD5Str(androidId)); + } + // 初始化异常捕获类 + //AppCrashHandler.getInstance().init(this); + // 初始化账户基础信息 + AccountHelper.init(this); + // 初始化网络请求 + ApiHttpClient.init(this); + //初始化百度地图 + SDKInitializer.initialize(this); + DBManager.init(this); + + if (OSCSharedPreference.getInstance().hasShowUpdate()) {//如果已经更新过 + //如果版本大于更新过的版本,就设置弹出更新 + if (BuildConfig.VERSION_CODE > OSCSharedPreference.getInstance().getUpdateVersion()) { + OSCSharedPreference.getInstance().putShowUpdate(true); + } + } + } + + /** + * 获取已读状态管理器 + * + * @param mark 传入标示,如:博客:blog; 新闻:news + * @return 已读状态管理器 + */ + public static ReadState getReadState(String mark) { + ReadStateHelper helper = ReadStateHelper.create(getInstance(), + CONFIG_READ_STATE_PRE + mark, 100); + return new ReadState(helper); + } + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + MultiDex.install(this); + } + + /** + * 一个已读状态管理器 + */ + public static class ReadState { + private ReadStateHelper helper; + + ReadState(ReadStateHelper helper) { + this.helper = helper; + } + + /** + * 添加已读状态 + * + * @param key 一般为资讯等Id + */ + public void put(long key) { + helper.put(key); + } + + /** + * 添加已读状态 + * + * @param key 一般为资讯等Id + */ + public void put(String key) { + helper.put(key); + } + + /** + * 获取是否为已读 + * + * @param key 一般为资讯等Id + * @return True 已读 + */ + public boolean already(long key) { + return helper.already(key); + } + + /** + * 获取是否为已读 + * + * @param key 一般为资讯等Id + * @return True 已读 + */ + public boolean already(String key) { + return helper.already(key); + } + } +} diff --git a/app/src/main/java/net/oschina/app/Setting.java b/app/src/main/java/net/oschina/app/Setting.java new file mode 100644 index 0000000000000000000000000000000000000000..52da0d89cf3d06465a2331374b25ba19232c961b --- /dev/null +++ b/app/src/main/java/net/oschina/app/Setting.java @@ -0,0 +1,126 @@ +package net.oschina.app; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.v4.content.SharedPreferencesCompat; +import android.text.TextUtils; + +import net.oschina.app.util.TDevice; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ +public final class Setting { + private static final String KEY_SEVER_URL = "serverUrl"; + private static final String KEY_VERSION_CODE = "versionCode"; + public static final String KEY_APP_UNIQUE_ID = "appUniqueID"; + private static final String KEY_SYSTEM_CONFIG_TIMESTAMP = "systemConfigTimeStamp"; + private static final String KEY_LOCATION_INFO = "locationInfo"; + private static final String KEY_LOCATION_PERMISSION = "locationPermission"; + private static final String KEY_LOCATION_APP_CODE = "locationAppCode"; + private static final String KEY_SOFT_KEYBOARD_HEIGHT = "softKeyboardHeight"; + + public static SharedPreferences getSettingPreferences(Context context) { + return context.getSharedPreferences(Setting.class.getName(), Context.MODE_PRIVATE); + } + + public static boolean checkIsNewVersion(Context context) { + int saveVersionCode = getSaveVersionCode(context); + int currentVersionCode = TDevice.getVersionCode(); + if (saveVersionCode < currentVersionCode) { + updateSaveVersionCode(context, currentVersionCode); + return true; + } + return false; + } + + public static int getSaveVersionCode(Context context) { + SharedPreferences sp = getSettingPreferences(context); + return sp.getInt(KEY_VERSION_CODE, 0); + } + + private static int updateSaveVersionCode(Context context, int version) { + SharedPreferences sp = getSettingPreferences(context); + SharedPreferences.Editor editor = sp.edit().putInt(KEY_VERSION_CODE, version); + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); + return version; + } + + public static String getServerUrl(Context context) { + SharedPreferences sp = getSettingPreferences(context); + String url = sp.getString(KEY_SEVER_URL, null); + if (TextUtils.isEmpty(url)) { + String[] urls = BuildConfig.API_SERVER_URL.split(";"); + if (urls.length > 0) { + url = urls[0]; + } else { + url = "https://www.oschina.net/"; + } + updateServerUrl(context, url); + } + return url; + } + + public static void updateServerUrl(Context context, String url) { + SharedPreferences sp = getSettingPreferences(context); + SharedPreferences.Editor editor = sp.edit().putString(KEY_SEVER_URL, url); + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); + } + + public static void updateSystemConfigTimeStamp(Context context) { + SharedPreferences sp = getSettingPreferences(context); + SharedPreferences.Editor editor = sp.edit().putLong(KEY_SYSTEM_CONFIG_TIMESTAMP, + System.currentTimeMillis()); + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); + } + + public static long getSystemConfigTimeStamp(Context context) { + SharedPreferences sp = getSettingPreferences(context); + return sp.getLong(KEY_SYSTEM_CONFIG_TIMESTAMP, 0); + } + + public static void updateLocationInfo(Context context, boolean hasLocation) { + SharedPreferences sp = getSettingPreferences(context); + SharedPreferences.Editor editor = sp.edit().putBoolean(KEY_LOCATION_INFO, hasLocation); + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); + } + + public static boolean hasLocation(Context context) { + SharedPreferences sp = getSettingPreferences(context); + return sp.getBoolean(KEY_LOCATION_INFO, false); + } + + public static void updateLocationPermission(Context context, boolean hasPermission) { + SharedPreferences sp = getSettingPreferences(context); + SharedPreferences.Editor editor = sp.edit().putBoolean(KEY_LOCATION_PERMISSION, hasPermission); + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); + } + + public static boolean hasLocationPermission(Context context) { + SharedPreferences sp = getSettingPreferences(context); + return sp.getBoolean(KEY_LOCATION_PERMISSION, false); + } + + public static void updateLocationAppCode(Context context, int appCode) { + SharedPreferences sp = getSettingPreferences(context); + SharedPreferences.Editor editor = sp.edit().putInt(KEY_LOCATION_APP_CODE, appCode); + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); + } + + public static int hasLocationAppCode(Context context) { + SharedPreferences sp = getSettingPreferences(context); + return sp.getInt(KEY_LOCATION_APP_CODE, 0); + } + + public static int getSoftKeyboardHeight(Context context) { + SharedPreferences sp = getSettingPreferences(context); + return sp.getInt(KEY_SOFT_KEYBOARD_HEIGHT, 0); + } + + public static void updateSoftKeyboardHeight(Context context, int height) { + SharedPreferences sp = getSettingPreferences(context); + SharedPreferences.Editor editor = sp.edit().putInt(KEY_SOFT_KEYBOARD_HEIGHT, height); + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); + } +} diff --git a/app/src/main/java/net/oschina/app/adapter/ActiveAdapter.java b/app/src/main/java/net/oschina/app/adapter/ActiveAdapter.java deleted file mode 100644 index 8b0b3400a970e9a0e024d58c164fe1d4febbcbaf..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/adapter/ActiveAdapter.java +++ /dev/null @@ -1,222 +0,0 @@ -package net.oschina.app.adapter; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.text.Html; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.style.ImageSpan; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import net.oschina.app.R; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.Active; -import net.oschina.app.bean.Active.ObjectReply; -import net.oschina.app.emoji.InputHelper; -import net.oschina.app.ui.ImagePreviewActivity; -import net.oschina.app.util.BitmapHelper; -import net.oschina.app.util.ImageUtils; -import net.oschina.app.util.PlatfromUtil; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.UIHelper; -import net.oschina.app.widget.AvatarView; -import net.oschina.app.widget.MyLinkMovementMethod; -import net.oschina.app.widget.MyURLSpan; -import net.oschina.app.widget.TweetTextView; - -import org.kymjs.kjframe.Core; -import org.kymjs.kjframe.bitmap.BitmapCallBack; -import org.kymjs.kjframe.utils.DensityUtils; - -import butterknife.ButterKnife; -import butterknife.InjectView; - -public class ActiveAdapter extends ListBaseAdapter { - private final static String AT_HOST_PRE = "http://my.oschina.net"; - private final static String MAIN_HOST = "http://www.oschina.net"; - - public ActiveAdapter() { - } - - private Bitmap recordBitmap; - private int rectSize; - - private void initRecordImg(Context cxt) { - recordBitmap = BitmapFactory.decodeResource(cxt.getResources(), - R.drawable.audio3); - recordBitmap = ImageUtils.zoomBitmap(recordBitmap, - DensityUtils.dip2px(cxt, 20f), DensityUtils.dip2px(cxt, 20f)); - } - - private void initImageSize(Context cxt) { - if (cxt != null && rectSize == 0) { - rectSize = (int) cxt.getResources().getDimension(R.dimen.space_100); - } else { - rectSize = 300; - } - } - - @Override - @SuppressLint("InflateParams") - protected View getRealView(int position, View convertView, - final ViewGroup parent) { - ViewHolder vh; - initImageSize(parent.getContext()); - if (convertView == null || convertView.getTag() == null) { - convertView = getLayoutInflater(parent.getContext()).inflate( - R.layout.list_cell_active, null); - vh = new ViewHolder(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } - - final Active item = (Active) mDatas.get(position); - - vh.name.setText(item.getAuthor()); - - vh.action.setText(UIHelper.parseActiveAction(item.getObjectType(), - item.getObjectCatalog(), item.getObjectTitle())); - - if (TextUtils.isEmpty(item.getMessage())) { - vh.body.setVisibility(View.GONE); - } else { - vh.body.setMovementMethod(MyLinkMovementMethod.a()); - vh.body.setFocusable(false); - vh.body.setDispatchToParent(true); - vh.body.setLongClickable(false); - - Spanned span = Html.fromHtml(modifyPath(item.getMessage())); - - if (!StringUtils.isEmpty(item.getTweetattach())) { - if (recordBitmap == null) { - initRecordImg(parent.getContext()); - } - ImageSpan recordImg = new ImageSpan(parent.getContext(), - recordBitmap); - SpannableString str = new SpannableString("c"); - str.setSpan(recordImg, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - vh.body.setText(str); - span = InputHelper.displayEmoji(parent.getContext() - .getResources(), span); - vh.body.append(span); - } else { - span = InputHelper.displayEmoji(parent.getContext() - .getResources(), span); - vh.body.setText(span); - } - MyURLSpan.parseLinkText(vh.body, span); - } - - ObjectReply reply = item.getObjectReply(); - if (reply != null) { - vh.reply.setMovementMethod(MyLinkMovementMethod.a()); - vh.reply.setFocusable(false); - vh.reply.setDispatchToParent(true); - vh.reply.setLongClickable(false); - Spanned span = UIHelper.parseActiveReply(reply.objectName, - reply.objectBody); - vh.reply.setText(span);// - MyURLSpan.parseLinkText(vh.reply, span); - vh.lyReply.setVisibility(TextView.VISIBLE); - } else { - vh.reply.setText(""); - vh.lyReply.setVisibility(TextView.GONE); - } - - vh.time.setText(StringUtils.friendly_time(item.getPubDate())); - - PlatfromUtil.setPlatFromString(vh.from, item.getAppClient()); - - vh.commentCount.setText(item.getCommentCount() + ""); - - vh.avatar.setUserInfo(item.getAuthorId(), item.getAuthor()); - vh.avatar.setAvatarUrl(item.getPortrait()); - - if (!TextUtils.isEmpty(item.getTweetimage())) { - setTweetImage(parent, vh, item); - } else { - vh.pic.setVisibility(View.GONE); - vh.pic.setImageBitmap(null); - } - - return convertView; - } - - /** - * 动态设置图片显示样式 - */ - private void setTweetImage(final ViewGroup parent, final ViewHolder vh, - final Active item) { - vh.pic.setVisibility(View.VISIBLE); - - new Core.Builder().url(item.getTweetimage()).view(vh.pic).loadBitmapRes(R.drawable - .pic_bg).size(rectSize, rectSize).bitmapCallBack(new BitmapCallBack() { - @Override - public void onSuccess(Bitmap bitmap) { - super.onSuccess(bitmap); - if (bitmap != null) { - bitmap = BitmapHelper.scaleWithXY(bitmap, rectSize / bitmap.getHeight()); - vh.pic.setImageBitmap(bitmap); - } - } - }).doTask(); - - vh.pic.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - ImagePreviewActivity.showImagePrivew(parent.getContext(), 0, - new String[]{getOriginalUrl(item.getTweetimage())}); - } - }); - } - - private String modifyPath(String message) { - message = message.replaceAll("(]+href=\")/([\\S]+)\"", "$1" - + AT_HOST_PRE + "/$2\""); - message = message.replaceAll( - "(]+href=\")http://m.oschina.net([\\S]+)\"", "$1" - + MAIN_HOST + "$2\""); - return message; - } - - private String getOriginalUrl(String url) { - return url.replaceAll("_thumb", ""); - } - - static class ViewHolder { - @InjectView(R.id.tv_name) - TextView name; - @InjectView(R.id.tv_from) - TextView from; - @InjectView(R.id.tv_time) - TextView time; - @InjectView(R.id.tv_action) - TextView action; - @InjectView(R.id.tv_action_name) - TextView actionName; - @InjectView(R.id.tv_comment_count) - TextView commentCount; - @InjectView(R.id.tv_body) - TweetTextView body; - @InjectView(R.id.tv_reply) - TweetTextView reply; - @InjectView(R.id.iv_pic) - ImageView pic; - @InjectView(R.id.ly_reply) - View lyReply; - @InjectView(R.id.iv_avatar) - AvatarView avatar; - - public ViewHolder(View view) { - ButterKnife.inject(this, view); - } - } -} diff --git a/app/src/main/java/net/oschina/app/adapter/BlogAdapter.java b/app/src/main/java/net/oschina/app/adapter/BlogAdapter.java index 41d6734ba65fc0b33ce3aa20f0c124be14a8ec60..87fa48ba49042fb6a61f74fec948e656478ff39b 100644 --- a/app/src/main/java/net/oschina/app/adapter/BlogAdapter.java +++ b/app/src/main/java/net/oschina/app/adapter/BlogAdapter.java @@ -5,41 +5,38 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import net.oschina.app.AppContext; import net.oschina.app.R; import net.oschina.app.base.ListBaseAdapter; import net.oschina.app.bean.Blog; -import net.oschina.app.bean.BlogList; import net.oschina.app.util.StringUtils; import net.oschina.app.util.ThemeSwitchUtils; +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; /** * @author HuangWenwei - * * @date 2014年9月29日 */ public class BlogAdapter extends ListBaseAdapter { static class ViewHolder { - @InjectView(R.id.tv_title) + @Bind(R.id.tv_title) TextView title; - @InjectView(R.id.tv_description) + @Bind(R.id.tv_description) TextView description; - @InjectView(R.id.tv_source) + @Bind(R.id.tv_source) TextView source; - @InjectView(R.id.tv_time) + @Bind(R.id.tv_time) TextView time; - @InjectView(R.id.tv_comment_count) + @Bind(R.id.tv_comment_count) TextView comment_count; - @InjectView(R.id.iv_tip) + @Bind(R.id.iv_tip) ImageView tip; public ViewHolder(View view) { - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); } } @@ -59,15 +56,14 @@ public class BlogAdapter extends ListBaseAdapter { vh.tip.setVisibility(View.VISIBLE); if (blog.getDocumenttype() == Blog.DOC_TYPE_ORIGINAL) { - vh.tip.setImageResource(R.drawable.widget_original_icon); + vh.tip.setImageResource(R.mipmap.widget_original_icon); } else { - vh.tip.setImageResource(R.drawable.widget_repaste_icon); + vh.tip.setImageResource(R.mipmap.widget_repaste_icon); } vh.title.setText(blog.getTitle()); - if (AppContext.isOnReadedPostList(BlogList.PREF_READED_BLOG_LIST, - blog.getId() + "")) { + if (false) { vh.title.setTextColor(parent.getContext().getResources() .getColor(ThemeSwitchUtils.getTitleReadedColor())); } else { @@ -83,7 +79,7 @@ public class BlogAdapter extends ListBaseAdapter { } vh.source.setText(blog.getAuthor()); - vh.time.setText(StringUtils.friendly_time(blog.getPubDate())); + vh.time.setText(StringUtils.formatSomeAgo(blog.getPubDate())); vh.comment_count.setText(blog.getCommentCount() + ""); return convertView; } diff --git a/app/src/main/java/net/oschina/app/adapter/CommentAdapter.java b/app/src/main/java/net/oschina/app/adapter/CommentAdapter.java deleted file mode 100644 index 4fda914f919a72bd9c5d9d058ae7bf24c74dfe75..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/adapter/CommentAdapter.java +++ /dev/null @@ -1,160 +0,0 @@ -package net.oschina.app.adapter; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.text.Html; -import android.text.Spanned; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -import net.oschina.app.R; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.Comment; -import net.oschina.app.bean.Comment.Refer; -import net.oschina.app.bean.Comment.Reply; -import net.oschina.app.emoji.InputHelper; -import net.oschina.app.util.PlatfromUtil; -import net.oschina.app.util.StringUtils; -import net.oschina.app.widget.AvatarView; -import net.oschina.app.widget.FloorView; -import net.oschina.app.widget.MyLinkMovementMethod; -import net.oschina.app.widget.MyURLSpan; -import net.oschina.app.widget.TweetTextView; - -import java.util.List; - -import butterknife.ButterKnife; -import butterknife.InjectView; - -public class CommentAdapter extends ListBaseAdapter { - - @SuppressLint({ "InflateParams", "CutPasteId" }) - @Override - protected View getRealView(int position, View convertView, - final ViewGroup parent) { - ViewHolder vh = null; - if (convertView == null || convertView.getTag() == null) { - convertView = getLayoutInflater(parent.getContext()).inflate( - R.layout.list_cell_comment, null); - vh = new ViewHolder(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } - try { - - final Comment item = mDatas.get(position); - - // 若Authorid为0,则显示非会员 - vh.name.setText(item.getAuthor() - + (item.getAuthorId() == 0 ? "(非会员)" : "")); - - vh.content.setMovementMethod(MyLinkMovementMethod.a()); - vh.content.setFocusable(false); - vh.content.setDispatchToParent(true); - vh.content.setLongClickable(false); - Spanned span = Html.fromHtml(TweetTextView.modifyPath(item - .getContent())); - span = InputHelper.displayEmoji(parent.getContext().getResources(), - span.toString()); - vh.content.setText(span); - MyURLSpan.parseLinkText(vh.content, span); - - vh.time.setText(StringUtils.friendly_time(item.getPubDate())); - - PlatfromUtil.setPlatFromString(vh.from, item.getAppClient()); - - // setup refers - setupRefers(parent.getContext(), vh, item.getRefers()); - - // setup replies - setupReplies(parent.getContext(), vh, item.getReplies()); - - vh.avatar.setAvatarUrl(item.getPortrait()); - vh.avatar.setUserInfo(item.getAuthorId(), item.getAuthor()); - } catch (Exception e) { - } - return convertView; - } - - private void setupRefers(Context context, ViewHolder vh, List refers) { - vh.refers.removeAllViews(); - if (refers == null || refers.size() <= 0) { - vh.refers.setVisibility(View.GONE); - } else { - vh.refers.setVisibility(View.VISIBLE); - - vh.refers.setComments(refers); - } - } - - private void setupReplies(Context context, ViewHolder vh, - List replies) { - vh.relies.removeAllViews(); - if (replies == null || replies.size() <= 0) { - vh.relies.setVisibility(View.GONE); - } else { - vh.relies.setVisibility(View.VISIBLE); - - // add count layout - View countView = getLayoutInflater(context).inflate( - R.layout.list_cell_reply_count, null, false); - TextView count = (TextView) countView - .findViewById(R.id.tv_comment_reply_count); - count.setText(context.getResources().getString( - R.string.comment_reply_count, replies.size())); - vh.relies.addView(countView); - - // add reply item - for (Reply reply : replies) { - LinearLayout replyItemView = (LinearLayout) getLayoutInflater( - context).inflate(R.layout.list_cell_reply_name_content, - null, false); - - replyItemView.setOrientation(LinearLayout.HORIZONTAL); - - replyItemView - .setBackgroundResource(R.drawable.comment_background); - - TextView name = (TextView) replyItemView - .findViewById(R.id.tv_reply_name); - name.setText(reply.rauthor + ":"); - - TweetTextView replyContent = (TweetTextView) replyItemView - .findViewById(R.id.tv_reply_content); - replyContent.setMovementMethod(MyLinkMovementMethod.a()); - replyContent.setFocusable(false); - replyContent.setDispatchToParent(true); - replyContent.setLongClickable(false); - Spanned rcontent = Html.fromHtml(reply.rcontent); - replyContent.setText(rcontent); - MyURLSpan.parseLinkText(replyContent, rcontent); - - vh.relies.addView(replyItemView); - } - } - } - - static class ViewHolder { - @InjectView(R.id.iv_avatar) - AvatarView avatar; - @InjectView(R.id.tv_name) - TextView name; - @InjectView(R.id.tv_time) - TextView time; - @InjectView(R.id.tv_from) - TextView from; - @InjectView(R.id.tv_content) - TweetTextView content; - @InjectView(R.id.ly_relies) - LinearLayout relies; - @InjectView(R.id.ly_refers) - FloorView refers; - - ViewHolder(View view) { - ButterKnife.inject(this, view); - } - } -} diff --git a/app/src/main/java/net/oschina/app/adapter/EventAdapter.java b/app/src/main/java/net/oschina/app/adapter/EventAdapter.java index eb42ca3a4a7a03e0edbba812f6981ea9772bf8bb..5307c5f90b2ab8fc2346e3c89d8af6315b402a16 100644 --- a/app/src/main/java/net/oschina/app/adapter/EventAdapter.java +++ b/app/src/main/java/net/oschina/app/adapter/EventAdapter.java @@ -5,15 +5,15 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import com.bumptech.glide.Glide; + import net.oschina.app.R; import net.oschina.app.base.ListBaseAdapter; import net.oschina.app.bean.Event; import net.oschina.app.bean.EventList; -import org.kymjs.kjframe.Core; - +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; /** * 活动列表适配器 @@ -27,19 +27,19 @@ public class EventAdapter extends ListBaseAdapter { static class ViewHolder { - @InjectView(R.id.iv_event_status) + @Bind(R.id.iv_event_status) ImageView status; - @InjectView(R.id.iv_event_img) + @Bind(R.id.iv_event_img) ImageView img; - @InjectView(R.id.tv_event_title) + @Bind(R.id.tv_event_title) TextView title; - @InjectView(R.id.tv_event_time) + @Bind(R.id.tv_event_time) TextView time; - @InjectView(R.id.tv_event_spot) + @Bind(R.id.tv_event_spot) TextView spot; public ViewHolder(View view) { - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); } } @@ -52,7 +52,7 @@ public class EventAdapter extends ListBaseAdapter { ViewHolder vh = null; if (convertView == null || convertView.getTag() == null) { convertView = getLayoutInflater(parent.getContext()).inflate( - R.layout.list_cell_event, null); + R.layout.list_cell_event, parent, false); vh = new ViewHolder(convertView); convertView.setTag(vh); } else { @@ -62,8 +62,8 @@ public class EventAdapter extends ListBaseAdapter { Event item = mDatas.get(position); setEventStatus(item, vh); - - new Core.Builder().view(vh.img).url(item.getCover()).doTask(); + if (vh.img != null && vh.img.getContext() != null) + Glide.with(vh.img.getContext()).load(item.getCover()).into(vh.img); vh.title.setText(item.getTitle()); vh.time.setText(item.getStartTime()); vh.spot.setText(item.getSpot()); @@ -77,8 +77,7 @@ public class EventAdapter extends ListBaseAdapter { case EventList.EVENT_LIST_TYPE_NEW_EVENT: if (event.getApplyStatus() == Event.APPLYSTATUS_CHECKING || event.getApplyStatus() == Event.APPLYSTATUS_CHECKED) { - vh.status - .setImageResource(R.drawable.icon_event_status_checked); + vh.status.setImageResource(R.mipmap.icon_event_status_checked); vh.status.setVisibility(View.VISIBLE); } else { vh.status.setVisibility(View.GONE); @@ -86,12 +85,11 @@ public class EventAdapter extends ListBaseAdapter { break; case EventList.EVENT_LIST_TYPE_MY_EVENT: if (event.getApplyStatus() == Event.APPLYSTATUS_ATTEND) { - vh.status.setImageResource(R.drawable.icon_event_status_attend); + vh.status.setImageResource(R.mipmap.icon_event_status_attend); } else if (event.getStatus() == Event.EVNET_STATUS_APPLYING) { - vh.status - .setImageResource(R.drawable.icon_event_status_checked); + vh.status.setImageResource(R.mipmap.icon_event_status_checked); } else { - vh.status.setImageResource(R.drawable.icon_event_status_over); + vh.status.setImageResource(R.mipmap.icon_event_status_over); } vh.status.setVisibility(View.VISIBLE); break; diff --git a/app/src/main/java/net/oschina/app/adapter/EventApplyAdapter.java b/app/src/main/java/net/oschina/app/adapter/EventApplyAdapter.java index 2b8f0b3711991af070e17937e6978043d574b71e..b889493a022e26a619c7402edd448af19feb5e24 100644 --- a/app/src/main/java/net/oschina/app/adapter/EventApplyAdapter.java +++ b/app/src/main/java/net/oschina/app/adapter/EventApplyAdapter.java @@ -1,30 +1,31 @@ package net.oschina.app.adapter; -import net.oschina.app.R; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.Apply; -import net.oschina.app.widget.AvatarView; import android.annotation.SuppressLint; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.base.ListBaseAdapter; +import net.oschina.app.bean.Apply; +import net.oschina.app.widget.AvatarView; + +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; /** * 活动参会人员适配器 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年12月12日 下午8:10:43 - * */ public class EventApplyAdapter extends ListBaseAdapter { @SuppressLint("InflateParams") @Override protected View getRealView(int position, View convertView, - final ViewGroup parent) { + final ViewGroup parent) { ViewHolder vh = null; if (convertView == null || convertView.getTag() == null) { convertView = getLayoutInflater(parent.getContext()).inflate( @@ -48,18 +49,19 @@ public class EventApplyAdapter extends ListBaseAdapter { static class ViewHolder { - @InjectView(R.id.tv_name) + @Bind(R.id.tv_name) TextView name; - @InjectView(R.id.tv_desc) + @Bind(R.id.tv_desc) TextView desc; - @InjectView(R.id.tv_from) TextView from; - @InjectView(R.id.iv_gender) + @Bind(R.id.tv_from) + TextView from; + @Bind(R.id.iv_gender) ImageView gender; - @InjectView(R.id.iv_avatar) + @Bind(R.id.iv_avatar) AvatarView avatar; public ViewHolder(View view) { - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); } } } diff --git a/app/src/main/java/net/oschina/app/adapter/FindUserAdapter.java b/app/src/main/java/net/oschina/app/adapter/FindUserAdapter.java deleted file mode 100644 index c6a538dc894c782f8c4c2d55ca55442ce764eb39..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/adapter/FindUserAdapter.java +++ /dev/null @@ -1,74 +0,0 @@ -package net.oschina.app.adapter; - -import net.oschina.app.R; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.User; -import net.oschina.app.widget.AvatarView; -import android.annotation.SuppressLint; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import butterknife.ButterKnife; -import butterknife.InjectView; - -/** - * 好友列表适配器 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年11月6日 上午11:22:27 - * - */ -public class FindUserAdapter extends ListBaseAdapter { - - @SuppressLint("InflateParams") - @Override - protected View getRealView(int position, View convertView, - final ViewGroup parent) { - ViewHolder vh = null; - if (convertView == null || convertView.getTag() == null) { - convertView = getLayoutInflater(parent.getContext()).inflate( - R.layout.list_cell_friend, null); - vh = new ViewHolder(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } - - final User item = (User) mDatas.get(position); - - vh.name.setText(item.getName()); - - vh.from.setText(item.getFrom()); - vh.desc.setVisibility(View.GONE); - int genderIcon = R.drawable.userinfo_icon_male; - if ("女".equals(item.getGender())) { - genderIcon = R.drawable.userinfo_icon_female; - } - - vh.gender.setImageResource(genderIcon); - - vh.avatar.setAvatarUrl(item.getPortrait()); - vh.avatar.setUserInfo(item.getId(), item.getName()); - - return convertView; - } - - static class ViewHolder { - - @InjectView(R.id.tv_name) - TextView name; - @InjectView(R.id.tv_from) - TextView from; - @InjectView(R.id.tv_desc) - TextView desc; - @InjectView(R.id.iv_gender) - ImageView gender; - @InjectView(R.id.iv_avatar) - AvatarView avatar; - - public ViewHolder(View view) { - ButterKnife.inject(this, view); - } - } -} diff --git a/app/src/main/java/net/oschina/app/adapter/FriendAdapter.java b/app/src/main/java/net/oschina/app/adapter/FriendAdapter.java deleted file mode 100644 index 2992593916d34ddfdc16d4f17fb0e5d67b62494e..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/adapter/FriendAdapter.java +++ /dev/null @@ -1,82 +0,0 @@ -package net.oschina.app.adapter; - -import net.oschina.app.R; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.Friend; -import net.oschina.app.util.StringUtils; -import net.oschina.app.widget.AvatarView; -import android.annotation.SuppressLint; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import butterknife.ButterKnife; -import butterknife.InjectView; - -/** - * 好友列表适配器 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年11月6日 上午11:22:27 - * - */ -public class FriendAdapter extends ListBaseAdapter { - - @SuppressLint("InflateParams") - @Override - protected View getRealView(int position, View convertView, - final ViewGroup parent) { - ViewHolder vh = null; - if (convertView == null || convertView.getTag() == null) { - convertView = getLayoutInflater(parent.getContext()).inflate( - R.layout.list_cell_friend, null); - vh = new ViewHolder(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } - - final Friend item = mDatas.get(position); - - vh.name.setText(item.getName()); - String from = item.getFrom(); - if (from != null || !StringUtils.isEmpty(from)) { - vh.from.setText(from); - } else { - vh.from.setVisibility(View.GONE); - } - String desc = item.getExpertise(); - if (desc != null || !StringUtils.isEmpty(from) || !"<无>".equals(desc)) { - vh.desc.setText(item.getExpertise()); - } else { - vh.desc.setVisibility(View.GONE); - } - - vh.gender - .setImageResource(item.getGender() == 1 ? R.drawable.userinfo_icon_male - : R.drawable.userinfo_icon_female); - - vh.avatar.setAvatarUrl(item.getPortrait()); - vh.avatar.setUserInfo(item.getUserid(), item.getName()); - - return convertView; - } - - static class ViewHolder { - - @InjectView(R.id.tv_name) - TextView name; - @InjectView(R.id.tv_from) - TextView from; - @InjectView(R.id.tv_desc) - TextView desc; - @InjectView(R.id.iv_gender) - ImageView gender; - @InjectView(R.id.iv_avatar) - AvatarView avatar; - - public ViewHolder(View view) { - ButterKnife.inject(this, view); - } - } -} diff --git a/app/src/main/java/net/oschina/app/adapter/MessageAdapter.java b/app/src/main/java/net/oschina/app/adapter/MessageAdapter.java index 4a0c74d2f21792d6bbe484f5f84a2a81465e29a5..5963b8a85f973efd957305b6af70b002b3365016 100644 --- a/app/src/main/java/net/oschina/app/adapter/MessageAdapter.java +++ b/app/src/main/java/net/oschina/app/adapter/MessageAdapter.java @@ -1,21 +1,23 @@ package net.oschina.app.adapter; -import net.oschina.app.AppContext; +import android.text.Html; +import android.text.Spanned; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + import net.oschina.app.R; import net.oschina.app.base.ListBaseAdapter; import net.oschina.app.bean.Messages; +import net.oschina.app.improve.account.AccountHelper; import net.oschina.app.util.StringUtils; import net.oschina.app.widget.AvatarView; import net.oschina.app.widget.MyLinkMovementMethod; import net.oschina.app.widget.MyURLSpan; import net.oschina.app.widget.TweetTextView; -import android.text.Html; -import android.text.Spanned; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; + +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; public class MessageAdapter extends ListBaseAdapter { @@ -26,7 +28,7 @@ public class MessageAdapter extends ListBaseAdapter { @Override protected View getRealView(int position, View convertView, - final ViewGroup parent) { + final ViewGroup parent) { ViewHolder vh = null; if (convertView == null || convertView.getTag() == null) { convertView = getLayoutInflater(parent.getContext()).inflate( @@ -39,7 +41,7 @@ public class MessageAdapter extends ListBaseAdapter { final Messages item = (Messages) mDatas.get(position); - if (AppContext.getInstance().getLoginUid() == item.getSenderId()) { + if (AccountHelper.getUserId() == item.getSenderId()) { vh.sender.setVisibility(View.VISIBLE); } else { vh.sender.setVisibility(View.GONE); @@ -55,9 +57,8 @@ public class MessageAdapter extends ListBaseAdapter { vh.content.setText(span); MyURLSpan.parseLinkText(vh.content, span); - vh.time.setText(StringUtils.friendly_time(item.getPubDate())); - vh.count.setText(parent.getResources().getString( - R.string.message_count, item.getMessageCount())); + vh.time.setText(StringUtils.formatSomeAgo(item.getPubDate())); + vh.count.setText(parent.getResources().getString(R.string.message_count, item.getMessageCount() + "")); vh.avatar.setAvatarUrl(item.getPortrait()); vh.avatar.setUserInfo(item.getSenderId(), item.getSender()); @@ -65,21 +66,21 @@ public class MessageAdapter extends ListBaseAdapter { } static class ViewHolder { - @InjectView(R.id.iv_avatar) + @Bind(R.id.iv_avatar) AvatarView avatar; - @InjectView(R.id.tv_name) + @Bind(R.id.tv_name) TextView name; - @InjectView(R.id.tv_sender) + @Bind(R.id.tv_sender) TextView sender; - @InjectView(R.id.tv_time) + @Bind(R.id.tv_time) TextView time; - @InjectView(R.id.tv_count) + @Bind(R.id.tv_count) TextView count; - @InjectView(R.id.tv_content) + @Bind(R.id.tv_content) TweetTextView content; ViewHolder(View view) { - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); } } } diff --git a/app/src/main/java/net/oschina/app/adapter/MessageDetailAdapter.java b/app/src/main/java/net/oschina/app/adapter/MessageDetailAdapter.java deleted file mode 100644 index f93e83a2758ece46bba2e99af8193566b25895ff..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/adapter/MessageDetailAdapter.java +++ /dev/null @@ -1,253 +0,0 @@ -package net.oschina.app.adapter; - -import android.text.Html; -import android.text.Spanned; -import android.view.View; -import android.view.ViewGroup; -import android.widget.IconTextView; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.api.ApiHttpClient; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.MessageDetail; -import net.oschina.app.emoji.InputHelper; -import net.oschina.app.util.ChatImageDisplayer; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.UIHelper; -import net.oschina.app.widget.AvatarView; -import net.oschina.app.widget.MyLinkMovementMethod; -import net.oschina.app.widget.MyURLSpan; -import net.oschina.app.widget.TweetTextView; - -import org.kymjs.kjframe.Core; -import org.kymjs.kjframe.KJBitmap; -import org.kymjs.kjframe.bitmap.BitmapConfig; -import org.kymjs.kjframe.http.HttpConfig; - -import java.lang.reflect.Field; - -import butterknife.ButterKnife; -import butterknife.InjectView; -import butterknife.OnClick; - -/** - * 铂金小鸟 2015-09-16 修改(聊天界面改进) - */ -public class MessageDetailAdapter extends ListBaseAdapter { - - KJBitmap mKjBitmap = Core.getKJBitmap(); - - private OnRetrySendMessageListener mOnRetrySendMessageListener; - - @Override - protected boolean loadMoreHasBg() { - return false; - } - - public MessageDetailAdapter() { - try { - //初始化display,设置图片的最大宽高和最小宽高 - ChatImageDisplayer displayer = new ChatImageDisplayer(new BitmapConfig()); - int maxWidth = TDevice.getDisplayMetrics().widthPixels / 2; - int maxHeight = maxWidth; - int minWidth = maxWidth / 2; - int minHeight = minWidth; - displayer.setImageSize(maxWidth, maxHeight, minWidth, minHeight); - //kjBitmap 不能设置自定义的displayer,这里通过反射设置自定义的displayer - Class classType = mKjBitmap.getClass(); - Field field = classType.getDeclaredField("displayer"); - field.setAccessible(true); - field.set(mKjBitmap, displayer); - } catch (Exception e) { - e.printStackTrace(); - } - } - - @Override - protected View getRealView(int position, View convertView, - final ViewGroup parent) { - final MessageDetail item = mDatas.get(mDatas.size() - position - 1); - - int itemType = 0; - if (item.getAuthorId() == AppContext.getInstance().getLoginUid()) { - itemType = 1; - } - boolean needCreateView = false; - ViewHolder vh = null; - if (convertView == null) { - needCreateView = true; - } else { - vh = (ViewHolder) convertView.getTag(); - } - - if (vh == null || vh.type != itemType) { - needCreateView = true; - } - - if (needCreateView) { - convertView = getLayoutInflater(parent.getContext()).inflate( - itemType == 0 ? R.layout.list_cell_chat_from : R.layout.list_cell_chat_to, - null); - vh = new ViewHolder(convertView); - vh.type = itemType; - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } - - vh.avatar.setAvatarUrl(item.getPortrait()); - vh.avatar.setUserInfo(item.getAuthorId(), item.getAuthor()); - - //判断是不是图片 - if (item.getBtype() == 3) { - showImage(vh, item); - } else /*if(item.getBtype()==1)*/ { - //文本消息 - showText(vh, item); - } - showStatus(vh, item); - - //检查是否需要显示时间 - if (item.isShowDate()) { - vh.time.setText(StringUtils.friendly_time3(item.getPubDate())); - vh.time.setVisibility(View.VISIBLE); - } else { - vh.time.setVisibility(View.GONE); - } - - return convertView; - } - - /** - * 显示文字消息 - * - * @param vh - * @param msg - */ - private void showText(ViewHolder vh, MessageDetail msg) { - vh.image.setVisibility(View.GONE); - vh.content.setVisibility(View.VISIBLE); - Spanned span = Html.fromHtml(msg.getContent()); - span = InputHelper.displayEmoji(vh.content.getResources(), span); - vh.content.setText(span); - MyURLSpan.parseLinkText(vh.content, span); - } - - /** - * 显示图片 - * - * @param vh - * @param msg - */ - private void showImage(ViewHolder vh, MessageDetail msg) { - vh.content.setVisibility(View.GONE); - vh.image.setVisibility(View.VISIBLE); - //加载图片 - vh.image.setImageResource(R.drawable.load_img_loading); - HttpConfig.sCookie = ApiHttpClient.getCookie(AppContext.getInstance()); - mKjBitmap.display(vh.image, msg.getContent(), R.drawable.load_img_error, 0, 0, - null); - } - - /** - * 显示消息状态 - * - * @param vh - * @param msg - */ - private void showStatus(ViewHolder vh, MessageDetail msg) { - //如果msg正在发送,则显示progressBar. 发送错误显示错误图标 - if (msg.getStatus() != null && msg.getStatus() != MessageDetail.MessageStatus.NORMAL) { - vh.msgStatusPanel.setVisibility(View.VISIBLE); - if (msg.getStatus() == MessageDetail.MessageStatus.SENDING) { - //sending 正在发送 - vh.progressBar.setVisibility(View.VISIBLE); - vh.error.setVisibility(View.GONE); - vh.error.setTag(null); - } else { - //error 发送出错 - vh.progressBar.setVisibility(View.GONE); - vh.error.setVisibility(View.VISIBLE); - //设置tag为msg id,以便点击重试发送 - vh.error.setTag(msg.getId()); - - } - } else { - //注意,此处隐藏要用INVISIBLE,不能使用GONE - vh.msgStatusPanel.setVisibility(View.INVISIBLE); - vh.error.setTag(null); - } - } - - public OnRetrySendMessageListener getOnRetrySendMessageListener() { - return mOnRetrySendMessageListener; - } - - public void setOnRetrySendMessageListener(OnRetrySendMessageListener - onRetrySendMessageListener) { - this.mOnRetrySendMessageListener = onRetrySendMessageListener; - } - - @Override - protected boolean hasFooterView() { - return false; - } - - class ViewHolder { - int type; - @InjectView(R.id.iv_avatar) - AvatarView avatar; - @InjectView(R.id.tv_time) - TextView time; - @InjectView(R.id.tv_content) - TweetTextView content; - @InjectView(R.id.iv_img) - ImageView image; - @InjectView(R.id.progress) - ProgressBar progressBar; - @InjectView(R.id.rl_msg_status_panel) - RelativeLayout msgStatusPanel; - @InjectView(R.id.itv_error) - IconTextView error; - - ViewHolder(View view) { - ButterKnife.inject(this, view); - - content.setMovementMethod(MyLinkMovementMethod.a()); - content.setFocusable(false); - content.setDispatchToParent(true); - content.setLongClickable(false); - } - - @OnClick(R.id.iv_img) - void viewImage(View v) { - if (v.getTag() != null) { - String url = (String) v.getTag(); - UIHelper.showImagePreview(v.getContext(), new String[]{url}); - } - } - - /** - * 重试发送 - * - * @param v - */ - @OnClick(R.id.itv_error) - void retry(View v) { - if (v.getTag() != null && mOnRetrySendMessageListener != null) { - mOnRetrySendMessageListener.onRetrySendMessage((int) v.getTag()); - } - } - } - - - public interface OnRetrySendMessageListener { - void onRetrySendMessage(int msgId); - } -} diff --git a/app/src/main/java/net/oschina/app/adapter/NewsAdapter.java b/app/src/main/java/net/oschina/app/adapter/NewsAdapter.java index 19382b7e166493e41a057dbb60252dd98a1a8332..fa504013001bfc25fa9e29c0bfadb758e4c3d438 100644 --- a/app/src/main/java/net/oschina/app/adapter/NewsAdapter.java +++ b/app/src/main/java/net/oschina/app/adapter/NewsAdapter.java @@ -6,16 +6,14 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import net.oschina.app.AppContext; import net.oschina.app.R; import net.oschina.app.base.ListBaseAdapter; import net.oschina.app.bean.News; -import net.oschina.app.bean.NewsList; import net.oschina.app.util.StringUtils; import net.oschina.app.util.ThemeSwitchUtils; +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; public class NewsAdapter extends ListBaseAdapter { @@ -35,8 +33,7 @@ public class NewsAdapter extends ListBaseAdapter { News news = mDatas.get(position); vh.title.setText(news.getTitle()); - if (AppContext.isOnReadedPostList(NewsList.PREF_READED_NEWS_LIST, - news.getId() + "")) { + if (false) { vh.title.setTextColor(parent.getContext().getResources() .getColor(ThemeSwitchUtils.getTitleReadedColor())); } else { @@ -57,28 +54,28 @@ public class NewsAdapter extends ListBaseAdapter { } else { vh.tip.setVisibility(View.GONE); } - vh.time.setText(StringUtils.friendly_time(news.getPubDate())); + vh.time.setText(StringUtils.formatSomeAgo(news.getPubDate())); vh.comment_count.setText(news.getCommentCount() + ""); return convertView; } static class ViewHolder { - @InjectView(R.id.tv_title) + @Bind(R.id.tv_title) TextView title; - @InjectView(R.id.tv_description) + @Bind(R.id.tv_description) TextView description; - @InjectView(R.id.tv_source) + @Bind(R.id.tv_source) TextView source; - @InjectView(R.id.tv_time) + @Bind(R.id.tv_time) TextView time; - @InjectView(R.id.tv_comment_count) + @Bind(R.id.tv_comment_count) TextView comment_count; - @InjectView(R.id.iv_tip) + @Bind(R.id.iv_tip) ImageView tip; public ViewHolder(View view) { - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); } } } diff --git a/app/src/main/java/net/oschina/app/adapter/PostAdapter.java b/app/src/main/java/net/oschina/app/adapter/PostAdapter.java index f51d60b36c361e2f52b4353fbba7aece3a8a16ce..e3722e2d3f9e41a245e53c8bd56e01b2bf4858bb 100644 --- a/app/src/main/java/net/oschina/app/adapter/PostAdapter.java +++ b/app/src/main/java/net/oschina/app/adapter/PostAdapter.java @@ -4,18 +4,16 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import net.oschina.app.AppContext; import net.oschina.app.R; import net.oschina.app.base.ListBaseAdapter; import net.oschina.app.bean.Post; -import net.oschina.app.bean.PostList; import net.oschina.app.util.HTMLUtil; import net.oschina.app.util.StringUtils; import net.oschina.app.util.ThemeSwitchUtils; import net.oschina.app.widget.AvatarView; +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; /** * post(讨论区帖子)适配器 @@ -27,22 +25,22 @@ public class PostAdapter extends ListBaseAdapter { static class ViewHolder { - @InjectView(R.id.tv_title) + @Bind(R.id.tv_title) TextView title; - @InjectView(R.id.tv_description) + @Bind(R.id.tv_description) TextView description; - @InjectView(R.id.tv_author) + @Bind(R.id.tv_author) TextView author; - @InjectView(R.id.tv_date) + @Bind(R.id.tv_date) TextView time; - @InjectView(R.id.tv_count) + @Bind(R.id.tv_count) TextView comment_count; - @InjectView(R.id.iv_face) + @Bind(R.id.iv_face) public AvatarView face; public ViewHolder(View view) { - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); } } @@ -70,8 +68,7 @@ public class PostAdapter extends ListBaseAdapter { vh.description.setText(HTMLUtil.replaceTag(post.getBody()).trim()); } - if (AppContext.isOnReadedPostList(PostList.PREF_READED_POST_LIST, - post.getId() + "")) { + if (false) { vh.title.setTextColor(parent.getContext().getResources() .getColor(ThemeSwitchUtils.getTitleReadedColor())); } else { @@ -80,7 +77,7 @@ public class PostAdapter extends ListBaseAdapter { } vh.author.setText(post.getAuthor()); - vh.time.setText(StringUtils.friendly_time(post.getPubDate())); + vh.time.setText(StringUtils.formatSomeAgo(post.getPubDate())); vh.comment_count.setText(post.getAnswerCount() + "回 / " + post.getViewCount() + "阅"); diff --git a/app/src/main/java/net/oschina/app/adapter/RecycleBin.java b/app/src/main/java/net/oschina/app/adapter/RecycleBin.java deleted file mode 100644 index e46514b864d4bdf989d5d762e4901e6f7e8b7435..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/adapter/RecycleBin.java +++ /dev/null @@ -1,155 +0,0 @@ -package net.oschina.app.adapter; - -import android.annotation.SuppressLint; -import android.os.Build; -import android.util.SparseArray; -import android.view.View; - -/** - * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of - * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the - * start of a layout. By construction, they are displaying current information. At the end of - * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that - * could potentially be used by the adapter to avoid allocating views unnecessarily. - *

    - * This class was taken from Android's implementation of {@link android.widget.AbsListView} which - * is copyrighted 2006 The Android Open Source Project. - */ -public class RecycleBin { - /** - * Views that were on screen at the start of layout. This array is populated at the start of - * layout, and at the end of layout all view in activeViews are moved to scrapViews. - * Views in activeViews represent a contiguous range of Views, with position of the first - * view store in mFirstActivePosition. - */ - private View[] activeViews = new View[0]; - private int[] activeViewTypes = new int[0]; - - /** Unsorted views that can be used by the adapter as a convert view. */ - private SparseArray[] scrapViews; - - private int viewTypeCount; - - private SparseArray currentScrapViews; - - public void setViewTypeCount(int viewTypeCount) { - if (viewTypeCount < 1) { - throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); - } - //noinspection unchecked - SparseArray[] scrapViews = new SparseArray[viewTypeCount]; - for (int i = 0; i < viewTypeCount; i++) { - scrapViews[i] = new SparseArray(); - } - this.viewTypeCount = viewTypeCount; - currentScrapViews = scrapViews[0]; - this.scrapViews = scrapViews; - } - - protected boolean shouldRecycleViewType(int viewType) { - return viewType >= 0; - } - - /** @return A view from the ScrapViews collection. These are unordered. */ - View getScrapView(int position, int viewType) { - if (viewTypeCount == 1) { - return retrieveFromScrap(currentScrapViews, position); - } else if (viewType >= 0 && viewType < scrapViews.length) { - return retrieveFromScrap(scrapViews[viewType], position); - } - return null; - } - - /** - * Put a view into the ScrapViews list. These views are unordered. - * - * @param scrap The view to add - */ - @SuppressLint("NewApi") -void addScrapView(View scrap, int position, int viewType) { - if (viewTypeCount == 1) { - currentScrapViews.put(position, scrap); - } else { - scrapViews[viewType].put(position, scrap); - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - scrap.setAccessibilityDelegate(null); - } - } - - /** Move all views remaining in activeViews to scrapViews. */ - @SuppressLint("NewApi") -void scrapActiveViews() { - final View[] activeViews = this.activeViews; - final int[] activeViewTypes = this.activeViewTypes; - final boolean multipleScraps = viewTypeCount > 1; - - SparseArray scrapViews = currentScrapViews; - final int count = activeViews.length; - for (int i = count - 1; i >= 0; i--) { - final View victim = activeViews[i]; - if (victim != null) { - int whichScrap = activeViewTypes[i]; - - activeViews[i] = null; - activeViewTypes[i] = -1; - - if (!shouldRecycleViewType(whichScrap)) { - continue; - } - - if (multipleScraps) { - scrapViews = this.scrapViews[whichScrap]; - } - scrapViews.put(i, victim); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - victim.setAccessibilityDelegate(null); - } - } - } - - pruneScrapViews(); - } - - /** - * Makes sure that the size of scrapViews does not exceed the size of activeViews. - * (This can happen if an adapter does not recycle its views). - */ - private void pruneScrapViews() { - final int maxViews = activeViews.length; - final int viewTypeCount = this.viewTypeCount; - final SparseArray[] scrapViews = this.scrapViews; - for (int i = 0; i < viewTypeCount; ++i) { - final SparseArray scrapPile = scrapViews[i]; - int size = scrapPile.size(); - final int extras = size - maxViews; - size--; - for (int j = 0; j < extras; j++) { - scrapPile.remove(scrapPile.keyAt(size--)); - } - } - } - - static View retrieveFromScrap(SparseArray scrapViews, int position) { - int size = scrapViews.size(); - if (size > 0) { - // See if we still have a view for this position. - for (int i = 0; i < size; i++) { - int fromPosition = scrapViews.keyAt(i); - View view = scrapViews.get(fromPosition); - if (fromPosition == position) { - scrapViews.remove(fromPosition); - return view; - } - } - int index = size - 1; - View r = scrapViews.valueAt(index); - scrapViews.remove(scrapViews.keyAt(index)); - return r; - } else { - return null; - } - } -} diff --git a/app/src/main/java/net/oschina/app/adapter/RecyclingPagerAdapter.java b/app/src/main/java/net/oschina/app/adapter/RecyclingPagerAdapter.java deleted file mode 100644 index 84e7d8859c0061d5d9fac6f4250a5765765bc807..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/adapter/RecyclingPagerAdapter.java +++ /dev/null @@ -1,122 +0,0 @@ -package net.oschina.app.adapter; - -import android.support.v4.view.PagerAdapter; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; - -/** - * A {@link PagerAdapter} which behaves like an {@link android.widget.Adapter} - * with view types and view recycling. - */ -public abstract class RecyclingPagerAdapter extends PagerAdapter { - static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE; - - private final RecycleBin recycleBin; - - public RecyclingPagerAdapter() { - this(new RecycleBin()); - } - - RecyclingPagerAdapter(RecycleBin recycleBin) { - this.recycleBin = recycleBin; - recycleBin.setViewTypeCount(getViewTypeCount()); - } - - @Override - public void notifyDataSetChanged() { - recycleBin.scrapActiveViews(); - super.notifyDataSetChanged(); - } - - @Override - public final Object instantiateItem(ViewGroup container, int position) { - int viewType = getItemViewType(position); - View view = null; - if (viewType != IGNORE_ITEM_VIEW_TYPE) { - view = recycleBin.getScrapView(position, viewType); - } - view = getView(position, view, container); - container.addView(view); - return view; - } - - @Override - public final void destroyItem(ViewGroup container, int position, - Object object) { - View view = (View) object; - container.removeView(view); - int viewType = getItemViewType(position); - if (viewType != IGNORE_ITEM_VIEW_TYPE) { - recycleBin.addScrapView(view, position, viewType); - } - } - - @Override - public final boolean isViewFromObject(View view, Object object) { - return view == object; - } - - /** - *

    - * Returns the number of types of Views that will be created by - * {@link #getView}. Each type represents a set of views that can be - * converted in {@link #getView}. If the adapter always returns the same - * type of View for all items, this method should return 1. - *

    - *

    - * This method will only be called when when the adapter is set on the the - * {@link AdapterView}. - *

    - * - * @return The number of types of Views that will be created by this adapter - */ - public int getViewTypeCount() { - return 1; - } - - /** - * Get the type of View that will be created by {@link #getView} for the - * specified item. - * - * @param position - * The position of the item within the adapter's data set whose - * view type we want. - * @return An integer representing the type of View. Two views should share - * the same type if one can be converted to the other in - * {@link #getView}. Note: Integers must be in the range 0 to - * {@link #getViewTypeCount} - 1. {@link #IGNORE_ITEM_VIEW_TYPE} can - * also be returned. - * @see #IGNORE_ITEM_VIEW_TYPE - */ - public int getItemViewType(int position) { - return 0; - } - - /** - * Get a View that displays the data at the specified position in the data - * set. You can either create a View manually or inflate it from an XML - * layout file. When the View is inflated, the parent View (GridView, - * ListView...) will apply default layout parameters unless you use - * {@link android.view.LayoutInflater#inflate(int, android.view.ViewGroup, boolean)} - * to specify a root view and to prevent attachment to the root. - * - * @param position - * The position of the item within the adapter's data set of the - * item whose view we want. - * @param convertView - * The old view to reuse, if possible. Note: You should check - * that this view is non-null and of an appropriate type before - * using. If it is not possible to convert this view to display - * the correct data, this method can create a new view. - * Heterogeneous lists can specify their number of view types, so - * that this View is always of the right type (see - * {@link #getViewTypeCount()} and {@link #getItemViewType(int)} - * ). - * @param parent - * The parent that this view will eventually be attached to - * @return A View corresponding to the data at the specified position. - */ - public abstract View getView(int position, View convertView, - ViewGroup container); -} diff --git a/app/src/main/java/net/oschina/app/adapter/SearchAdapter.java b/app/src/main/java/net/oschina/app/adapter/SearchAdapter.java deleted file mode 100644 index 9e77e3c0b86ede480514a34f114568f9c5d4b984..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/adapter/SearchAdapter.java +++ /dev/null @@ -1,70 +0,0 @@ -package net.oschina.app.adapter; - -import net.oschina.app.R; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.SearchResult; -import net.oschina.app.util.StringUtils; -import android.annotation.SuppressLint; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import butterknife.ButterKnife; -import butterknife.InjectView; - -public class SearchAdapter extends ListBaseAdapter { - - @SuppressLint("InflateParams") - @Override - protected View getRealView(int position, View convertView, ViewGroup parent) { - ViewHolder vh = null; - if (convertView == null || convertView.getTag() == null) { - convertView = getLayoutInflater(parent.getContext()).inflate( - R.layout.list_cell_news, null); - vh = new ViewHolder(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } - - SearchResult item = (SearchResult) mDatas.get(position); - - vh.title.setText(item.getTitle()); - - if (item.getDescription() == null || StringUtils.isEmpty(item.getDescription())) { - vh.description.setVisibility(View.GONE); - } else { - vh.description.setVisibility(View.VISIBLE); - vh.description.setText(item.getDescription()); - } - - if (!StringUtils.isEmpty(item.getAuthor())) { - vh.source.setText(item.getAuthor()); - } else { - vh.source.setVisibility(View.GONE); - } - - if (!StringUtils.isEmpty(item.getPubDate())) { - vh.time.setText(StringUtils.friendly_time(item.getPubDate())); - } else { - vh.time.setVisibility(View.GONE); - } - - vh.tip.setVisibility(View.GONE); - vh.comment_count.setVisibility(View.GONE); - return convertView; - } - - static class ViewHolder { - @InjectView(R.id.tv_title)TextView title; - @InjectView(R.id.tv_description)TextView description; - @InjectView(R.id.tv_source)TextView source; - @InjectView(R.id.tv_time)TextView time; - @InjectView(R.id.tv_comment_count)TextView comment_count; - @InjectView(R.id.iv_tip)ImageView tip; - - public ViewHolder(View view) { - ButterKnife.inject(this, view); - } - } -} diff --git a/app/src/main/java/net/oschina/app/adapter/SearchFriendAdapter.java b/app/src/main/java/net/oschina/app/adapter/SearchFriendAdapter.java deleted file mode 100644 index 313cc6c8703140fb5a651c0c9074d256cf566115..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/adapter/SearchFriendAdapter.java +++ /dev/null @@ -1,111 +0,0 @@ -package net.oschina.app.adapter; - -import android.content.Context; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.style.ForegroundColorSpan; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.CheckBox; -import android.widget.TextView; - -import net.oschina.app.R; -import net.oschina.app.ui.SelectFriendsActivity; -import net.oschina.app.widget.AvatarView; - -import java.util.List; - -import butterknife.ButterKnife; -import butterknife.InjectView; - -/** - *

    Created 15/8/27 下午9:29.

    - *

    Email:730395591@qq.com

    - *

    LeonLee Blog

    - * - * @author 李文龙(LeonLee - * - * 搜索好友结果适配器 - */ -public class SearchFriendAdapter extends BaseAdapter { - - final List mSearchResults; - private LayoutInflater mInflater; - - public SearchFriendAdapter(List list) { - this.mSearchResults = list; - } - - protected LayoutInflater getLayoutInflater(Context context) { - if (mInflater == null) { - mInflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - return mInflater; - } - - @Override - public int getCount() { - return mSearchResults.size(); - } - - @Override - public SelectFriendsActivity.SearchItem getItem(int position) { - return mSearchResults.get(position); - } - - @Override - public long getItemId(int position) { - return 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final NormalViewHolder holder; - if(convertView == null) { - convertView = getLayoutInflater(parent.getContext()).inflate(R.layout.list_cell_select_friend, parent, false); - holder = new NormalViewHolder(convertView); - convertView.setTag(holder); - } else { - holder = (NormalViewHolder) convertView.getTag(); - } - holder.bind(getItem(position)); - return convertView; - } - - static class NormalViewHolder { - @InjectView(R.id.tv_name) - TextView name; - @InjectView(R.id.iv_avatar) - AvatarView avatar; - @InjectView(R.id.cb_check) - CheckBox checkBox; - - NormalViewHolder(View view) { - ButterKnife.inject(this, view); - avatar.setClickable(false); - } - - public void bind(SelectFriendsActivity.SearchItem item) { - SelectFriendsActivity.FriendItem friendItem = item.getFriendItem(); - avatar.setAvatarUrl(friendItem.getFriend().getPortrait()); - checkBox.setChecked(friendItem.isSelected()); - - avatar.setDisplayCircle(false); - - int start = item.getStartIndex(); - if(start != -1) { - SpannableString ss = new SpannableString(friendItem.getFriend().getName()); - ss.setSpan(new ForegroundColorSpan(item.getHightLightColor()), start, - start + item.getKeyLength(), Spanned.SPAN_EXCLUSIVE_INCLUSIVE); - name.setText(ss); - } else { - name.setText(friendItem.getFriend().getName()); - } - - } - } - -} diff --git a/app/src/main/java/net/oschina/app/adapter/SelectFriendAdapter.java b/app/src/main/java/net/oschina/app/adapter/SelectFriendAdapter.java deleted file mode 100644 index b2b85e972781371137a62b545c46f9c97e5a3184..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/adapter/SelectFriendAdapter.java +++ /dev/null @@ -1,219 +0,0 @@ -package net.oschina.app.adapter; - -import android.content.Context; -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.CheckBox; -import android.widget.TextView; - -import net.oschina.app.R; -import net.oschina.app.ui.SelectFriendsActivity; -import net.oschina.app.widget.AvatarView; - -import java.util.ArrayList; -import java.util.List; - -import butterknife.ButterKnife; -import butterknife.InjectView; - -/** - *

    Created 15/8/26 下午2:09.

    - *

    Email:730395591@qq.com

    - *

    LeonLee Blog

    - * - * @author 李文龙(LeonLee) - */ -public class SelectFriendAdapter extends BaseAdapter { - - private static final char DEFAULT_OTHER_LETTER = '#'; - - private static final int VIEWTYPE_HEADER = 0; - private static final int VIEWTYPE_NORMAL = 1; - - private List mList = new ArrayList<>(); - //记录索引值对应的位置 - private SparseArray mPositionArray = new SparseArray<>(); - - private LayoutInflater mInflater; - - public SelectFriendAdapter() { - - } - - protected LayoutInflater getLayoutInflater(Context context) { - if (mInflater == null) { - mInflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - return mInflater; - } - - public void setFriendItems(List list) { - mPositionArray.clear(); - mList.clear(); - - //非字母开头的列表 - List otherList = null; - - char lastIndex = '0'; - for(SelectFriendsActivity.FriendItem item : list) { - char indexLetter; - char c = item.getFirstLetter(); - if(c >= 'A' && c <= 'Z') { - indexLetter = c; - } else if(c >= 'a' && c <= 'z') { - indexLetter = (char)(c - 'a' + 'A'); - } else { - indexLetter = DEFAULT_OTHER_LETTER; - } - if(indexLetter == DEFAULT_OTHER_LETTER) { - if(otherList == null) { - otherList = new ArrayList<>(); - } - otherList.add(new NormalItemData(item)); - } else { - if (indexLetter != lastIndex) { - mPositionArray.append(indexLetter, mList.size()); - mList.add(new HeaderItemData(String.valueOf(indexLetter))); - lastIndex = indexLetter; - } - mList.add(new NormalItemData(item)); - } - } - //将没有索引的数据列表放到最后 - if(otherList != null && !otherList.isEmpty()) { - mPositionArray.append(DEFAULT_OTHER_LETTER, mList.size()); - mList.add(new HeaderItemData(String.valueOf(DEFAULT_OTHER_LETTER))); - mList.addAll(otherList); - } - - notifyDataSetChanged(); - } - - /** 根据索引获取位置*/ - public int getPositionByIndex(char indexLetter) { - Integer value = mPositionArray.get(indexLetter); - if(value == null) { - return -1; - } - return value; - } - - public SelectFriendsActivity.FriendItem getFriendItem(int position) { - ItemData data = getItem(position); - if(data instanceof NormalItemData) { - return ((NormalItemData)data).friendItem; - } - return null; - } - - @Override - public int getCount() { - return mList.size(); - } - - @Override - public ItemData getItem(int position) { - return mList.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public int getViewTypeCount() { - return 2; - } - - @Override - public int getItemViewType(int position) { - return (getItem(position) instanceof HeaderItemData) ? VIEWTYPE_HEADER : VIEWTYPE_NORMAL; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final ItemData itemData = getItem(position); - if(itemData instanceof HeaderItemData) { - final HeaderViewHolder holder; - if(convertView == null) { - convertView = getLayoutInflater(parent.getContext()).inflate(R.layout.list_index_header, parent, false); - holder = new HeaderViewHolder(convertView); - convertView.setTag(holder); - convertView.setEnabled(false); - } else { - holder = (HeaderViewHolder) convertView.getTag(); - } - holder.bind(((HeaderItemData)itemData).index); - } else { - final NormalViewHolder holder; - if(convertView == null) { - convertView = getLayoutInflater(parent.getContext()).inflate(R.layout.list_cell_select_friend, parent, false); - holder = new NormalViewHolder(convertView); - convertView.setTag(holder); - } else { - holder = (NormalViewHolder) convertView.getTag(); - } - holder.bind(((NormalItemData)itemData).friendItem); - } - - return convertView; - } - - private interface ItemData {} - - static class HeaderItemData implements ItemData { - String index; - public HeaderItemData(String index) { - this.index = index; - } - } - - static class NormalItemData implements ItemData { - SelectFriendsActivity.FriendItem friendItem; - - public NormalItemData(SelectFriendsActivity.FriendItem friendItem) { - this.friendItem = friendItem; - } - } - - static class HeaderViewHolder { - @InjectView(R.id.header_text) - TextView text; - - HeaderViewHolder(View view) { - ButterKnife.inject(this, view); - } - - public void bind(String index) { - text.setText(index); - } - } - - static class NormalViewHolder { - @InjectView(R.id.tv_name) - TextView name; - @InjectView(R.id.iv_avatar) - AvatarView avatar; - @InjectView(R.id.cb_check) - CheckBox checkBox; - - NormalViewHolder(View view) { - ButterKnife.inject(this, view); - avatar.setClickable(false); - } - - public void bind(SelectFriendsActivity.FriendItem item) { - name.setText(item.getFriend().getName()); - avatar.setAvatarUrl(item.getFriend().getPortrait()); - checkBox.setChecked(item.isSelected()); - - avatar.setDisplayCircle(false); - - } - } -} diff --git a/app/src/main/java/net/oschina/app/adapter/SoftwareAdapter.java b/app/src/main/java/net/oschina/app/adapter/SoftwareAdapter.java index 2aea0ca4d3ae357829cbfe0d7ad42c8493b9dcf2..43aa942a9f24c78560655a4e2a26689a2df8983e 100644 --- a/app/src/main/java/net/oschina/app/adapter/SoftwareAdapter.java +++ b/app/src/main/java/net/oschina/app/adapter/SoftwareAdapter.java @@ -4,26 +4,24 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import net.oschina.app.AppContext; import net.oschina.app.R; import net.oschina.app.base.ListBaseAdapter; import net.oschina.app.bean.SoftwareDec; -import net.oschina.app.bean.SoftwareList; import net.oschina.app.util.ThemeSwitchUtils; +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; public class SoftwareAdapter extends ListBaseAdapter { static class ViewHold { - @InjectView(R.id.tv_title) + @Bind(R.id.tv_title) TextView name; - @InjectView(R.id.tv_software_des) + @Bind(R.id.tv_software_des) TextView des; public ViewHold(View view) { - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); } } @@ -42,8 +40,7 @@ public class SoftwareAdapter extends ListBaseAdapter { SoftwareDec softwareDes = (SoftwareDec) mDatas.get(position); vh.name.setText(softwareDes.getName()); - if (AppContext.isOnReadedPostList(SoftwareList.PREF_READED_SOFTWARE_LIST, - softwareDes.getName())) { + if (false) { vh.name.setTextColor(parent.getContext().getResources() .getColor(ThemeSwitchUtils.getTitleReadedColor())); } else { diff --git a/app/src/main/java/net/oschina/app/adapter/SoftwareCatalogListAdapter.java b/app/src/main/java/net/oschina/app/adapter/SoftwareCatalogListAdapter.java index 68a4b02716ad43bdf64f1f5427285a796ae15e99..ca6be25740e46a39197c40f70996a31ad9684473 100644 --- a/app/src/main/java/net/oschina/app/adapter/SoftwareCatalogListAdapter.java +++ b/app/src/main/java/net/oschina/app/adapter/SoftwareCatalogListAdapter.java @@ -1,39 +1,44 @@ package net.oschina.app.adapter; -import net.oschina.app.R; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.SoftwareCatalogList.SoftwareType; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.base.ListBaseAdapter; +import net.oschina.app.bean.SoftwareCatalogList.SoftwareType; + +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; public class SoftwareCatalogListAdapter extends ListBaseAdapter { - - static class ViewHold{ - @InjectView(R.id.tv_software_catalog_name)TextView name; - public ViewHold(View view) { - ButterKnife.inject(this,view); - } - } - - @Override - protected View getRealView(int position, View convertView, ViewGroup parent) { - - ViewHold vh = null; - - if (convertView == null || convertView.getTag() == null) { - convertView = getLayoutInflater(parent.getContext()).inflate(R.layout.list_cell_softwarecatalog, null); - vh = new ViewHold(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHold)convertView.getTag(); - } - - SoftwareType softwareType = (SoftwareType) mDatas.get(position); - vh.name.setText(softwareType.getName()); - return convertView; - - } + + static class ViewHold { + @Bind(R.id.tv_software_catalog_name) + TextView name; + + public ViewHold(View view) { + ButterKnife.bind(this, view); + } + } + + @Override + protected View getRealView(int position, View convertView, ViewGroup parent) { + + ViewHold vh = null; + + if (convertView == null || convertView.getTag() == null) { + convertView = getLayoutInflater(parent.getContext()).inflate(R.layout + .list_cell_softwarecatalog, null); + vh = new ViewHold(convertView); + convertView.setTag(vh); + } else { + vh = (ViewHold) convertView.getTag(); + } + + SoftwareType softwareType = (SoftwareType) mDatas.get(position); + vh.name.setText(softwareType.getName()); + return convertView; + + } } diff --git a/app/src/main/java/net/oschina/app/adapter/TweetAdapter.java b/app/src/main/java/net/oschina/app/adapter/TweetAdapter.java deleted file mode 100644 index 8fcb096eac337d80fc1ba44066eacb77eba515db..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/adapter/TweetAdapter.java +++ /dev/null @@ -1,251 +0,0 @@ -package net.oschina.app.adapter; - -import android.content.Context; -import android.content.DialogInterface; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.text.Html; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.style.ImageSpan; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import com.loopj.android.http.AsyncHttpResponseHandler; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.Tweet; -import net.oschina.app.emoji.InputHelper; -import net.oschina.app.ui.ImagePreviewActivity; -import net.oschina.app.util.DialogHelp; -import net.oschina.app.util.ImageUtils; -import net.oschina.app.util.KJAnimations; -import net.oschina.app.util.PlatfromUtil; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.TypefaceUtils; -import net.oschina.app.util.UIHelper; -import net.oschina.app.widget.AvatarView; -import net.oschina.app.widget.MyLinkMovementMethod; -import net.oschina.app.widget.TweetTextView; - -import org.kymjs.kjframe.Core; -import org.kymjs.kjframe.utils.DensityUtils; - -import butterknife.ButterKnife; -import butterknife.InjectView; -import cz.msebera.android.httpclient.Header; - -public class TweetAdapter extends ListBaseAdapter { - - static class ViewHolder { - @InjectView(R.id.tv_tweet_name) - TextView author; - @InjectView(R.id.tv_tweet_time) - TextView time; - @InjectView(R.id.tweet_item) - TweetTextView content; - @InjectView(R.id.tv_tweet_comment_count) - TextView commentcount; - @InjectView(R.id.tv_tweet_platform) - TextView platform; - @InjectView(R.id.iv_tweet_face) - AvatarView face; - @InjectView(R.id.iv_tweet_image) - ImageView image; - @InjectView(R.id.tv_like_state) - TextView tvLikeState; - @InjectView(R.id.tv_del) - TextView del; - @InjectView(R.id.tv_likeusers) - TextView likeUsers; - - public ViewHolder(View view) { - ButterKnife.inject(this, view); - image.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - String url = (String) v.getTag(); - int index = url.lastIndexOf("?"); - if (index > 0) { - url = url.substring(0, index); - } - ImagePreviewActivity.showImagePrivew(v.getContext(), 0, new String[]{url}); - } - }); - } - } - - private Bitmap recordBitmap; - private Context context; - - final private AsyncHttpResponseHandler handler = new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) { - } - }; - - private void initRecordImg(Context cxt) { - recordBitmap = BitmapFactory.decodeResource(cxt.getResources(), - R.drawable.audio3); - recordBitmap = ImageUtils.zoomBitmap(recordBitmap, - DensityUtils.dip2px(cxt, 20f), DensityUtils.dip2px(cxt, 20f)); - } - - @Override - protected View getRealView(final int position, View convertView, ViewGroup parent) { - context = parent.getContext(); - final ViewHolder vh; - if (convertView == null || convertView.getTag() == null) { - convertView = View.inflate(context, R.layout.list_cell_tweet, null); - vh = new ViewHolder(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } - final Tweet tweet = mDatas.get(position); - - if (tweet.getAuthorid() == AppContext.getInstance().getLoginUid()) { - vh.del.setVisibility(View.VISIBLE); - vh.del.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - optionDel(context, tweet, position); - } - }); - } else { - vh.del.setVisibility(View.GONE); - } - - vh.face.setUserInfo(tweet.getAuthorid(), tweet.getAuthor()); - vh.face.setAvatarUrl(tweet.getPortrait()); - vh.author.setText(tweet.getAuthor()); - vh.time.setText(StringUtils.friendly_time(tweet.getPubDate())); - vh.content.setMovementMethod(MyLinkMovementMethod.a()); - vh.content.setFocusable(false); - vh.content.setDispatchToParent(true); - vh.content.setLongClickable(false); - - Spanned span = Html.fromHtml(tweet.getBody().trim()); - - if (!StringUtils.isEmpty(tweet.getAttach())) { - if (recordBitmap == null) { - initRecordImg(context); - } - ImageSpan recordImg = new ImageSpan(context, recordBitmap); - SpannableString str = new SpannableString("c"); - str.setSpan(recordImg, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - vh.content.setText(str); - span = InputHelper.displayEmoji(context.getResources(), span); - vh.content.append(span); - } else { - span = InputHelper.displayEmoji(context.getResources(), span); - vh.content.setText(span); - } - - vh.commentcount.setText(tweet.getCommentCount()); - - showTweetImage(vh, tweet.getImgSmall(), tweet.getImgBig()); - tweet.setLikeUsers(context, vh.likeUsers, true); - - if (tweet.getLikeUser() == null) { - vh.tvLikeState.setVisibility(View.GONE); - } else { - vh.tvLikeState.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (AppContext.getInstance().isLogin()) { - updateLikeState(vh, tweet); - } else { - AppContext.showToast("先登陆再赞~"); - UIHelper.showLoginActivity(context); - } - } - }); - } - - TypefaceUtils.setTypeface(vh.tvLikeState); - if (tweet.getIsLike() == 1) { - vh.tvLikeState.setTextColor(AppContext.getInstance().getResources().getColor(R.color - .day_colorPrimary)); - } else { - vh.tvLikeState.setTextColor(AppContext.getInstance().getResources().getColor(R.color - .gray)); - } - PlatfromUtil.setPlatFromString(vh.platform, tweet.getAppclient()); - return convertView; - } - - private void updateLikeState(ViewHolder vh, Tweet tweet) { - if (tweet.getIsLike() == 1) { - tweet.setIsLike(0); - tweet.setLikeCount(tweet.getLikeCount() - 1); - if (!tweet.getLikeUser().isEmpty()) { - tweet.getLikeUser().remove(0); - } - OSChinaApi.pubUnLikeTweet(tweet.getId(), tweet.getAuthorid(), - handler); - vh.tvLikeState.setTextColor(AppContext.getInstance().getResources().getColor(R.color - .gray)); - } else { - vh.tvLikeState.setAnimation(KJAnimations.getScaleAnimation(1.5f, 300)); - tweet.getLikeUser().add(0, AppContext.getInstance().getLoginUser()); - OSChinaApi.pubLikeTweet(tweet.getId(), tweet.getAuthorid(), handler); - vh.tvLikeState.setTextColor(AppContext.getInstance().getResources().getColor(R.color - .day_colorPrimary)); - tweet.setIsLike(1); - tweet.setLikeCount(tweet.getLikeCount() + 1); - } - tweet.setLikeUsers(context, vh.likeUsers, true); - } - - private void optionDel(Context context, final Tweet tweet, final int position) { - - DialogHelp.getConfirmDialog(context, "确定删除吗?", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - OSChinaApi.deleteTweet(tweet.getAuthorid(), tweet.getId(), - new AsyncHttpResponseHandler() { - @Override - public void onSuccess(int arg0, Header[] arg1, - byte[] arg2) { - mDatas.remove(position); - notifyDataSetChanged(); - } - - @Override - public void onFailure(int arg0, Header[] arg1, - byte[] arg2, Throwable arg3) { - } - }); - } - }).show(); - } - - /** - * 动态设置动弹列表图片显示规则 - */ - private void showTweetImage(final ViewHolder vh, String imgSmall, final String imgBig) { - if (!TextUtils.isEmpty(imgBig)) { - vh.image.setTag(imgBig); - new Core.Builder().view(vh.image).size(300, 300).url(imgBig + "?300X300") - .loadBitmapRes(R.drawable.pic_bg).doTask(); - vh.image.setVisibility(AvatarView.VISIBLE); - } else { - vh.image.setVisibility(AvatarView.GONE); - } - } -} diff --git a/app/src/main/java/net/oschina/app/adapter/TweetLikeAdapter.java b/app/src/main/java/net/oschina/app/adapter/TweetLikeAdapter.java deleted file mode 100644 index 2dc6aa678e1621568112a25b4b9b3ddad07960ee..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/adapter/TweetLikeAdapter.java +++ /dev/null @@ -1,87 +0,0 @@ -package net.oschina.app.adapter; - -import android.annotation.SuppressLint; -import android.text.Spanned; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import net.oschina.app.R; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.TweetLike; -import net.oschina.app.util.PlatfromUtil; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.UIHelper; -import net.oschina.app.widget.AvatarView; -import net.oschina.app.widget.MyLinkMovementMethod; -import net.oschina.app.widget.MyURLSpan; -import net.oschina.app.widget.TweetTextView; - -import butterknife.ButterKnife; -import butterknife.InjectView; - -/** - * 动弹点赞适配器 TweetLikeAdapter.java - * - * @author 火蚁(http://my.oschina.net/u/253900) - * @data 2015-4-10 上午10:19:19 - */ -public class TweetLikeAdapter extends ListBaseAdapter { - - @SuppressLint("InflateParams") - @Override - protected View getRealView(int position, View convertView, - final ViewGroup parent) { - ViewHolder vh = null; - if (convertView == null || convertView.getTag() == null) { - convertView = getLayoutInflater(parent.getContext()).inflate( - R.layout.list_cell_tweet_like, null); - vh = new ViewHolder(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } - - final TweetLike item = (TweetLike) mDatas.get(position); - - vh.name.setText(item.getUser().getName().trim()); - - vh.action.setText("赞了我的动弹"); - - vh.time.setText(StringUtils.friendly_time(item.getDatatime().trim())); - - PlatfromUtil.setPlatFromString(vh.from, item.getAppClient()); - vh.avatar.setUserInfo(item.getUser().getId(), item.getUser().getName()); - vh.avatar.setAvatarUrl(item.getUser().getPortrait()); - - vh.reply.setMovementMethod(MyLinkMovementMethod.a()); - vh.reply.setFocusable(false); - vh.reply.setDispatchToParent(true); - vh.reply.setLongClickable(false); - Spanned span = UIHelper.parseActiveReply(item.getTweet().getAuthor(), - item.getTweet().getBody()); - vh.reply.setText(span); - MyURLSpan.parseLinkText(vh.reply, span); - - return convertView; - } - - static class ViewHolder { - @InjectView(R.id.tv_name) - TextView name; - @InjectView(R.id.tv_from) - TextView from; - @InjectView(R.id.tv_time) - TextView time; - @InjectView(R.id.tv_action) - TextView action; - @InjectView(R.id.tv_reply) - TweetTextView reply; - @InjectView(R.id.iv_avatar) - AvatarView avatar; - - public ViewHolder(View view) { - ButterKnife.inject(this, view); - } - } -} diff --git a/app/src/main/java/net/oschina/app/adapter/TweetLikeUsersAdapter.java b/app/src/main/java/net/oschina/app/adapter/TweetLikeUsersAdapter.java deleted file mode 100644 index 96110041bf15d11281f5e348d6ec55b420bcfed4..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/adapter/TweetLikeUsersAdapter.java +++ /dev/null @@ -1,53 +0,0 @@ -package net.oschina.app.adapter; - -import butterknife.ButterKnife; -import butterknife.InjectView; - -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import net.oschina.app.R; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.User; -import net.oschina.app.widget.AvatarView; - -/** - * TweetLikeUsersAdapter.java - * - * @author 火蚁(http://my.oschina.net/u/253900) - * - * @data 2015-3-26 下午4:11:25 - */ -public class TweetLikeUsersAdapter extends ListBaseAdapter { - - @Override - protected View getRealView(int position, View convertView, ViewGroup parent) { - // TODO Auto-generated method stub - ViewHolder vh = null; - if (convertView == null || convertView.getTag() == null) { - convertView = getLayoutInflater(parent.getContext()).inflate( - R.layout.list_cell_tweet_like_user, null); - vh = new ViewHolder(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } - User item = mDatas.get(position); - vh.avatar.setAvatarUrl(item.getPortrait()); - vh.name.setText(item.getName()); - return convertView; - } - - static class ViewHolder { - - @InjectView(R.id.iv_avatar) - AvatarView avatar; - @InjectView(R.id.tv_name) - TextView name; - - public ViewHolder(View view) { - ButterKnife.inject(this, view); - } - } -} - diff --git a/app/src/main/java/net/oschina/app/adapter/UserFavoriteAdapter.java b/app/src/main/java/net/oschina/app/adapter/UserFavoriteAdapter.java index 405820f3e1f5c68f107ae293a523de19f049fe8d..6dd22b7abc64fbdac4587c7429324562cce1bf73 100644 --- a/app/src/main/java/net/oschina/app/adapter/UserFavoriteAdapter.java +++ b/app/src/main/java/net/oschina/app/adapter/UserFavoriteAdapter.java @@ -1,41 +1,44 @@ package net.oschina.app.adapter; -import net.oschina.app.R; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.Favorite; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.base.ListBaseAdapter; +import net.oschina.app.bean.Favorite; + +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; public class UserFavoriteAdapter extends ListBaseAdapter { - - static class ViewHolder { - - @InjectView(R.id.tv_favorite_title) TextView title; - - public ViewHolder(View view) { - ButterKnife.inject(this,view); - } - } - - @Override - protected View getRealView(int position, View convertView, ViewGroup parent) { - ViewHolder vh = null; - if (convertView == null || convertView.getTag() == null) { - convertView = getLayoutInflater(parent.getContext()).inflate( - R.layout.list_cell_favorite, null); - vh = new ViewHolder(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } - - Favorite favorite = (Favorite) mDatas.get(position); - - vh.title.setText(favorite.getTitle()); - return convertView; - } + + static class ViewHolder { + + @Bind(R.id.tv_favorite_title) + TextView title; + + public ViewHolder(View view) { + ButterKnife.bind(this, view); + } + } + + @Override + protected View getRealView(int position, View convertView, ViewGroup parent) { + ViewHolder vh = null; + if (convertView == null || convertView.getTag() == null) { + convertView = getLayoutInflater(parent.getContext()).inflate( + R.layout.list_cell_favorite, null); + vh = new ViewHolder(convertView); + convertView.setTag(vh); + } else { + vh = (ViewHolder) convertView.getTag(); + } + + Favorite favorite = (Favorite) mDatas.get(position); + + vh.title.setText(favorite.getTitle()); + return convertView; + } } diff --git a/app/src/main/java/net/oschina/app/adapter/ViewHolder.java b/app/src/main/java/net/oschina/app/adapter/ViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..73147f5861fe227cd70fe3b5e9bc7a3f05ab4797 --- /dev/null +++ b/app/src/main/java/net/oschina/app/adapter/ViewHolder.java @@ -0,0 +1,237 @@ +package net.oschina.app.adapter; + +import android.text.SpannableString; +import android.text.Spanned; +import android.text.TextUtils; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.util.ImageLoader; + +@SuppressWarnings("unused") +public class ViewHolder { + private SparseArray mViews; + private int mLayoutId; + private View mConvertView; + private int mPosition; + private Callback mCaller; + + public interface Callback { + RequestManager getImgLoader(); + + LayoutInflater getInflate(); + } + + + public ViewHolder(Callback caller, ViewGroup parent, int layoutId, int position) { + this.mViews = new SparseArray(); + this.mPosition = position; + this.mLayoutId = layoutId; + this.mCaller = caller; + LayoutInflater inflater = caller.getInflate(); + this.mConvertView = inflater.inflate(layoutId, parent, false); + this.mConvertView.setTag(this); + } + + /** + * 获取一个viewHolder + * + * @param caller Caller + * @param convertView view + * @param parent parent view + * @param layoutId 布局资源id + * @param position 索引 + * @return + */ + public static ViewHolder getViewHolder(Callback caller, View convertView, ViewGroup parent, int layoutId, int position) { + boolean needCreateView = false; + ViewHolder vh = null; + if (convertView == null) { + needCreateView = true; + } else { + vh = (ViewHolder) convertView.getTag(); + } + if (vh != null && (vh.mLayoutId != layoutId)) { + needCreateView = true; + } + if (needCreateView) { + return new ViewHolder(caller, parent, layoutId, position); + } + return (ViewHolder) convertView.getTag(); + } + + public int getPosition() { + return this.mPosition; + } + + public int getLayoutId() { + return mLayoutId; + } + + // 通过一个viewId来获取一个view + public T getView(int viewId) { + View view = mViews.get(viewId); + if (view == null) { + view = mConvertView.findViewById(viewId); + mViews.put(viewId, view); + } + return (T) view; + } + + // 返回viewHolder的容器类 + public View getConvertView() { + return this.mConvertView; + } + + // 给TextView设置文字 + public void setText(int viewId, String text) { + if (TextUtils.isEmpty(text)) return; + TextView tv = getView(viewId); + tv.setText(text); + tv.setVisibility(View.VISIBLE); + } + + // 给TextView设置文字 + public void setText(int viewId, SpannableString text) { + if (text == null) return; + TextView tv = getView(viewId); + tv.setText(text); + tv.setVisibility(View.VISIBLE); + } + + public void setTextColor(int viewId, int textColor) { + TextView tv = getView(viewId); + tv.setTextColor(textColor); + tv.setVisibility(View.VISIBLE); + } + + public void setText(int viewId, Spanned text) { + if (text == null) return; + TextView tv = getView(viewId); + tv.setText(text); + tv.setVisibility(View.VISIBLE); + } + + // 给TextView设置文字 + public void setText(int viewId, int textRes) { + TextView tv = getView(viewId); + tv.setText(textRes); + tv.setVisibility(View.VISIBLE); + } + + public void setText(int viewId, int textRes, int bgRes, int textColor) { + TextView tv = getView(viewId); + tv.setText(textRes); + tv.setVisibility(View.VISIBLE); + tv.setBackgroundResource(bgRes); + tv.setTextColor(textColor); + } + + public void setText(int viewId, String text, boolean gone) { + if (TextUtils.isEmpty(text) && gone) { + getView(viewId).setVisibility(View.GONE); + return; + } + setText(viewId, text); + } + + public void setText(int viewId, String text, int emptyRes) { + TextView tv = getView(viewId); + if (TextUtils.isEmpty(text)) { + tv.setText(emptyRes); + } else { + tv.setText(text); + } + tv.setVisibility(View.VISIBLE); + } + + public void setText(int viewId, String text, String emptyText) { + TextView tv = getView(viewId); + if (TextUtils.isEmpty(text)) { + tv.setText(emptyText); + } else { + tv.setText(text); + } + tv.setVisibility(View.VISIBLE); + } + + public void setImage(int viewId, int imgRes) { + ImageView iv = getView(viewId); + iv.setImageResource(imgRes); + } + + public void setImageForNet(int viewId, String imgUrl, int emptyRes) { + ImageView iv = getView(viewId); + RequestManager loader = mCaller.getImgLoader(); + ImageLoader.loadImage(loader, iv, imgUrl, emptyRes); + } + + public void setImageForNet(ImageView iv, String imgUrl, int emptyRes) { + RequestManager loader = mCaller.getImgLoader(); + ImageLoader.loadImage(loader, iv, imgUrl, emptyRes); + } + + public void setImageForNet(ImageView iv, String imgUrl, int emptyRes, int errorRes) { + RequestManager loader = mCaller.getImgLoader(); + ImageLoader.loadImage(loader, iv, imgUrl, emptyRes, errorRes); + } + + public void setImageForNet(int viewId, String imgUrl) { + setImageForNet(viewId, imgUrl, R.drawable.bg_normal); + } + + // 设置头像 + public void setPortrait(int viewId, String imgUrl) { + setImageForNet(viewId, imgUrl, R.drawable.bg_normal); + } + + public void setButtonText(int viewId, String text) { + Button button = getView(viewId); + button.setText(text); + } + + public void setOnClick(int viewId, View.OnClickListener onClickListener) { + View view = getView(viewId); + view.setOnClickListener(onClickListener); + } + + public void setGone(int viewId) { + getView(viewId).setVisibility(View.GONE); + } + + public void setVisibility(int viewId) { + getView(viewId).setVisibility(View.VISIBLE); + } + + public void setInVisibility(int viewId) { + getView(viewId).setVisibility(View.INVISIBLE); + } + + public boolean isVisibility(int viewId) { + return (getView(viewId).getVisibility()) == View.VISIBLE; + } + + public void setEnabled(int viewId) { + View view = getView(viewId); + view.setEnabled(true); + } + + public void setEnabled(int viewId, boolean isEnable) { + View view = getView(viewId); + view.setEnabled(isEnable); + } + + public void setDisEnabled(int viewId) { + View view = getView(viewId); + view.setEnabled(false); + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/adapter/ViewPageFragmentAdapter.java b/app/src/main/java/net/oschina/app/adapter/ViewPageFragmentAdapter.java index e216768150fde748f7c538d659053a2ca95c4800..e31601083f72c39a187551ce7acb22d27f6692b9 100644 --- a/app/src/main/java/net/oschina/app/adapter/ViewPageFragmentAdapter.java +++ b/app/src/main/java/net/oschina/app/adapter/ViewPageFragmentAdapter.java @@ -1,31 +1,36 @@ package net.oschina.app.adapter; -import java.util.ArrayList; - -import net.oschina.app.R; -import net.oschina.app.widget.PagerSlidingTabStrip; import android.annotation.SuppressLint; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.util.ArrayMap; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; +import net.oschina.app.R; +import net.oschina.app.widget.PagerSlidingTabStrip; + +import java.util.ArrayList; +import java.util.Map; + @SuppressLint("Recycle") public class ViewPageFragmentAdapter extends FragmentStatePagerAdapter { private final Context mContext; protected PagerSlidingTabStrip mPagerStrip; private final ViewPager mViewPager; - private final ArrayList mTabs = new ArrayList(); + public ArrayList mTabs = new ArrayList(); + private Map mFragments = new ArrayMap<>(); public ViewPageFragmentAdapter(FragmentManager fm, - PagerSlidingTabStrip pageStrip, ViewPager pager) { + PagerSlidingTabStrip pageStrip, ViewPager pager) { super(fm); mContext = pager.getContext(); mPagerStrip = pageStrip; @@ -50,12 +55,14 @@ public class ViewPageFragmentAdapter extends FragmentStatePagerAdapter { return; } - // 加入tab title - View v = LayoutInflater.from(mContext).inflate( - R.layout.base_viewpage_fragment_tab_item, null, false); - TextView title = (TextView) v.findViewById(R.id.tab_title); - title.setText(info.title); - mPagerStrip.addTab(v); + if (!TextUtils.isEmpty(info.title)) { + // 加入tab title + View v = LayoutInflater.from(mContext).inflate( + R.layout.base_viewpage_fragment_tab_item, null, false); + TextView title = (TextView) v.findViewById(R.id.tab_title); + title.setText(info.title); + mPagerStrip.addTab(v); + } mTabs.add(info); notifyDataSetChanged(); @@ -70,9 +77,8 @@ public class ViewPageFragmentAdapter extends FragmentStatePagerAdapter { /** * 移除一个tab - * - * @param index - * 备注:如果index小于0,则从第一个开始删 如果大于tab的数量值则从最后一个开始删除 + * + * @param index 备注:如果index小于0,则从第一个开始删 如果大于tab的数量值则从最后一个开始删除 */ public void remove(int index) { if (mTabs.isEmpty()) { @@ -84,6 +90,12 @@ public class ViewPageFragmentAdapter extends FragmentStatePagerAdapter { if (index >= mTabs.size()) { index = mTabs.size() - 1; } + + ViewPageInfo info = mTabs.get(index); + // 清理缓存 + if (mFragments.containsKey(info.tag)) + mFragments.remove(info.tag); + mTabs.remove(index); mPagerStrip.removeTab(index, 1); notifyDataSetChanged(); @@ -96,6 +108,7 @@ public class ViewPageFragmentAdapter extends FragmentStatePagerAdapter { if (mTabs.isEmpty()) { return; } + mFragments.clear(); mPagerStrip.removeAllTab(); mTabs.clear(); notifyDataSetChanged(); @@ -114,7 +127,15 @@ public class ViewPageFragmentAdapter extends FragmentStatePagerAdapter { @Override public Fragment getItem(int position) { ViewPageInfo info = mTabs.get(position); - return Fragment.instantiate(mContext, info.clss.getName(), info.args); + + Fragment fragment = mFragments.get(info.tag); + if (fragment == null) { + fragment = Fragment.instantiate(mContext, info.clss.getName(), info.args); + // 避免重复创建而进行缓存 + mFragments.put(info.tag, fragment); + } + + return fragment; } @Override diff --git a/app/src/main/java/net/oschina/app/adapter/ViewPageInfo.java b/app/src/main/java/net/oschina/app/adapter/ViewPageInfo.java index 61307db2ee4c5f03961c4449330b5529aed5edd5..0e188ebdcd93092a1129b317ec7e383cb3b22f47 100644 --- a/app/src/main/java/net/oschina/app/adapter/ViewPageInfo.java +++ b/app/src/main/java/net/oschina/app/adapter/ViewPageInfo.java @@ -4,13 +4,13 @@ import android.os.Bundle; public final class ViewPageInfo { - public final String tag; + public final String tag; public final Class clss; public final Bundle args; public final String title; public ViewPageInfo(String _title, String _tag, Class _class, Bundle _args) { - title = _title; + title = _title; tag = _tag; clss = _class; args = _args; diff --git a/app/src/main/java/net/oschina/app/api/APIVerify.java b/app/src/main/java/net/oschina/app/api/APIVerify.java new file mode 100644 index 0000000000000000000000000000000000000000..6a53f807f782abc1a6d77f5809f1f03e23ad2e15 --- /dev/null +++ b/app/src/main/java/net/oschina/app/api/APIVerify.java @@ -0,0 +1,21 @@ +package net.oschina.app.api; + +import net.oschina.app.BuildConfig; +import net.oschina.app.improve.utils.AES; +import net.oschina.app.improve.utils.MD5; + +/** + * API验证 + * Created by huanghaibin on 2018/4/13. + */ + +public final class APIVerify { + + public static String getVerifyString() { + if (BuildConfig.DEBUG) { + return MD5.get32MD5Str(AES.encryptByBase64(BuildConfig.APPLICATION_ID)); + } + return MD5.get32MD5Str(MD5.get32MD5Str(BuildConfig.AES_KEY) + MD5.get32MD5Str(BuildConfig.VERSION_NAME) + AES.encryptByBase64(BuildConfig.APPLICATION_ID)); + } + +} diff --git a/app/src/main/java/net/oschina/app/api/ApiClientHelper.java b/app/src/main/java/net/oschina/app/api/ApiClientHelper.java index f6da3026481e84c4c39d322bbb664e9d76ef2374..da899885718951979af358db993459fe35a946c4 100644 --- a/app/src/main/java/net/oschina/app/api/ApiClientHelper.java +++ b/app/src/main/java/net/oschina/app/api/ApiClientHelper.java @@ -1,22 +1,95 @@ package net.oschina.app.api; -import net.oschina.app.AppContext; - -public class ApiClientHelper { - - /** - * 获得请求的服务端数据的userAgent - * @param appContext - * @return - */ - public static String getUserAgent(AppContext appContext) { - StringBuilder ua = new StringBuilder("OSChina.NET"); - ua.append('/' + appContext.getPackageInfo().versionName + '_' - + appContext.getPackageInfo().versionCode);// app版本信息 - ua.append("/Android");// 手机系统平台 - ua.append("/" + android.os.Build.VERSION.RELEASE);// 手机系统版本 - ua.append("/" + android.os.Build.MODEL); // 手机型号 - ua.append("/" + appContext.getAppId());// 客户端唯一标识 - return ua.toString(); - } +import android.app.Application; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.support.v4.content.SharedPreferencesCompat; +import android.text.TextUtils; + +import net.oschina.app.Setting; + +import java.util.UUID; + +class ApiClientHelper { + + /** + * 获得请求的服务端数据的userAgent + * 客户端唯一标识 + * + * @param appContext + * @return + */ + static String getUserAgent(Application appContext) { + // WebSettings.getDefaultUserAgent(appContext) + + int vCode = getPackageInfo(appContext).versionCode; + String version = Build.VERSION.RELEASE; // "1.0" or "3.4b5" + String osVer = version.length() > 0 ? version : "1.0"; + + String model = Build.MODEL; + String id = Build.ID; // "MASTER" or "M4-rc20" + if (id.length() > 0) { + model += " Build/" + id; + } + + String format = "OSChina.NET/1.0 (oscapp; %s; Android %s; %s; %s)"; + String ua = String.format(format, vCode, osVer, model, getAppId(appContext)); + ApiHttpClient.log("getUserAgent:" + ua); + return ua; + } + + public static String getDefaultUserAgent() { + StringBuilder result = new StringBuilder(64); + result.append("Dalvik/"); + result.append(System.getProperty("java.vm.version")); // such as 1.1.0 + result.append(" (Linux; U; Android "); + + String version = Build.VERSION.RELEASE; // "1.0" or "3.4b5" + result.append(version.length() > 0 ? version : "1.0"); + + // add the model for the release build + if ("REL".equals(Build.VERSION.CODENAME)) { + String model = Build.MODEL; + if (model.length() > 0) { + result.append("; "); + result.append(model); + } + } + String id = Build.ID; // "MASTER" or "M4-rc20" + if (id.length() > 0) { + result.append(" Build/"); + result.append(id); + } + result.append(")"); + return result.toString(); + } + + private static String getAppId(Application context) { + if (context != null) { + SharedPreferences sp = Setting.getSettingPreferences(context); + String uniqueID = sp.getString(Setting.KEY_APP_UNIQUE_ID, null); + if (TextUtils.isEmpty(uniqueID)) { + uniqueID = UUID.randomUUID().toString(); + SharedPreferences.Editor editor = sp.edit().putString(Setting.KEY_APP_UNIQUE_ID, uniqueID); + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); + } + return uniqueID; + } + return UUID.randomUUID().toString(); + } + + private static PackageInfo getPackageInfo(Application context) { + PackageInfo info = null; + try { + info = context.getPackageManager() + .getPackageInfo(context.getPackageName(), 0); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(System.err); + } + if (info == null) + info = new PackageInfo(); + return info; + } } diff --git a/app/src/main/java/net/oschina/app/api/ApiHttpClient.java b/app/src/main/java/net/oschina/app/api/ApiHttpClient.java index 47e124aff7a4da23d0e3d9488cee70197dca2a3c..f2b35eff7c27329864d07b5a1e9b54a6bb282ca8 100644 --- a/app/src/main/java/net/oschina/app/api/ApiHttpClient.java +++ b/app/src/main/java/net/oschina/app/api/ApiHttpClient.java @@ -1,149 +1,362 @@ package net.oschina.app.api; +import android.annotation.SuppressLint; +import android.app.Application; import android.content.Context; -import android.util.Log; -import cz.msebera.android.httpclient.client.params.ClientPNames; +import android.os.SystemClock; +import android.text.TextUtils; import com.loopj.android.http.AsyncHttpClient; +import com.loopj.android.http.AsyncHttpRequest; import com.loopj.android.http.AsyncHttpResponseHandler; import com.loopj.android.http.RequestParams; +import com.loopj.android.http.ResponseHandlerInterface; import net.oschina.app.AppContext; +import net.oschina.app.Setting; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.git.api.API; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.improve.utils.MD5; +import net.oschina.app.util.TDevice; import net.oschina.app.util.TLog; +import java.io.IOException; +import java.net.Socket; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.Locale; -public class ApiHttpClient { +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import cz.msebera.android.httpclient.Header; +import cz.msebera.android.httpclient.client.CookieStore; +import cz.msebera.android.httpclient.client.methods.HttpUriRequest; +import cz.msebera.android.httpclient.client.params.ClientPNames; +import cz.msebera.android.httpclient.client.protocol.HttpClientContext; +import cz.msebera.android.httpclient.conn.ssl.SSLSocketFactory; +import cz.msebera.android.httpclient.cookie.Cookie; +import cz.msebera.android.httpclient.impl.client.AbstractHttpClient; +import cz.msebera.android.httpclient.impl.client.DefaultHttpClient; +import cz.msebera.android.httpclient.protocol.HttpContext; +@SuppressWarnings("WeakerAccess") +public class ApiHttpClient { public final static String HOST = "www.oschina.net"; - private static String API_URL = "http://www.oschina.net/%s"; -// public final static String HOST = "192.168.1.101"; -// private static String API_URL = "http://192.168.1.101/%s"; - public static final String DELETE = "DELETE"; - public static final String GET = "GET"; - public static final String POST = "POST"; - public static final String PUT = "PUT"; - public static AsyncHttpClient client; + //public final static String HOST = "www.oschina.tk"; + public static String API_URL = "https://www.oschina.net/%s"; + + static class ApiAsyncHttpClient extends AsyncHttpClient { + @Override + protected AsyncHttpRequest newAsyncHttpRequest(DefaultHttpClient client, HttpContext httpContext, HttpUriRequest uriRequest, String contentType, ResponseHandlerInterface responseHandler, Context context) { + return new CheckNetAsyncHttpRequest(client, httpContext, uriRequest, responseHandler); + } + } + + static class CheckNetAsyncHttpRequest extends AsyncHttpRequest { + public CheckNetAsyncHttpRequest(AbstractHttpClient client, HttpContext context, HttpUriRequest request, ResponseHandlerInterface responseHandler) { + super(client, context, request, responseHandler); + } - public ApiHttpClient() { + @Override + public void run() { + // 如果网络本身有问题则直接取消 + if (!TDevice.hasInternet()) { + new Thread() { + @Override + public void run() { + // 延迟一秒 + try { + SystemClock.sleep(1000); + cancel(true); + } catch (Exception e) { + e.printStackTrace(); + } + } + }.start(); + } + super.run(); + } } - public static AsyncHttpClient getHttpClient() { - return client; + private static AsyncHttpClient CLIENT; + + private ApiHttpClient() { + } + + public static void setHeaderNewsId() { + if (CLIENT == null) + return; + CLIENT.removeHeader("newsId"); + CLIENT.addHeader("newsId", String.valueOf(OSCSharedPreference.getInstance().getLastNewsId())); } - public static void cancelAll(Context context) { - client.cancelRequests(context, true); + /** + * 初始化网络请求,包括Cookie的初始化 + * + * @param context AppContext + */ + public static void init(Application context) { + API_URL = Setting.getServerUrl(context) + "%s"; + AsyncHttpClient client = new ApiAsyncHttpClient(); + client.setConnectTimeout(5 * 1000); + client.setResponseTimeout(7 * 1000); + //client.setCookieStore(new PersistentCookieStore(context)); + // Set + ApiHttpClient.setHttpClient(client); + // Set Cookie + setCookieHeader(AccountHelper.getCookie()); + } + + public static void cancelALL() { + try { + CLIENT.cancelAllRequests(true); + } catch (Exception e) { + e.printStackTrace(); + } } - public static void clearUserCookies(Context context) { - // (new HttpClientCookieStore(context)).a(); + public static AsyncHttpClient getHttpClient() { + return CLIENT; } public static void delete(String partUrl, AsyncHttpResponseHandler handler) { - client.delete(getAbsoluteApiUrl(partUrl), handler); - log(new StringBuilder("DELETE ").append(partUrl).toString()); + setHeaderNewsId(); + CLIENT.delete(getAbsoluteApiUrl(partUrl), handler); + log("DELETE " + partUrl); } public static void get(String partUrl, AsyncHttpResponseHandler handler) { - client.get(getAbsoluteApiUrl(partUrl), handler); - log(new StringBuilder("GET ").append(partUrl).toString()); + setHeaderNewsId(); + CLIENT.get(getAbsoluteApiUrl(partUrl), handler); + log("GET " + partUrl); } public static void get(String partUrl, RequestParams params, AsyncHttpResponseHandler handler) { - client.get(getAbsoluteApiUrl(partUrl), params, handler); - log(new StringBuilder("GET ").append(partUrl).append("&") - .append(params).toString()); + setHeaderNewsId(); + CLIENT.get(getAbsoluteApiUrl(partUrl), params, handler); + log("GET " + partUrl + "?" + params); } public static String getAbsoluteApiUrl(String partUrl) { + setHeaderNewsId(); String url = partUrl; if (!partUrl.startsWith("http:") && !partUrl.startsWith("https:")) { url = String.format(API_URL, partUrl); } - Log.d("BASE_CLIENT", "request:" + url); + log("request:" + url); return url; } - public static String getApiUrl() { - return API_URL; - } - public static void getDirect(String url, AsyncHttpResponseHandler handler) { - client.get(url, handler); - log(new StringBuilder("GET ").append(url).toString()); + setHeaderNewsId(); + CLIENT.get(url, handler); + log("GET " + url); } public static void log(String log) { - Log.d("BaseApi", log); - TLog.log("Test", log); + TLog.log("ApiHttpClient", log); } public static void post(String partUrl, AsyncHttpResponseHandler handler) { - client.post(getAbsoluteApiUrl(partUrl), handler); - log(new StringBuilder("POST ").append(partUrl).toString()); + setHeaderNewsId(); + CLIENT.post(getAbsoluteApiUrl(partUrl), handler); + log("POST " + partUrl); } public static void post(String partUrl, RequestParams params, AsyncHttpResponseHandler handler) { - client.post(getAbsoluteApiUrl(partUrl), params, handler); - log(new StringBuilder("POST ").append(partUrl).append("&") - .append(params).toString()); - } - - public static void postDirect(String url, RequestParams params, - AsyncHttpResponseHandler handler) { - client.post(url, params, handler); - log(new StringBuilder("POST ").append(url).append("&").append(params) - .toString()); + setHeaderNewsId(); + CLIENT.post(getAbsoluteApiUrl(partUrl), params, handler); + log("POST " + partUrl + "?" + params); } public static void put(String partUrl, AsyncHttpResponseHandler handler) { - client.put(getAbsoluteApiUrl(partUrl), handler); - log(new StringBuilder("PUT ").append(partUrl).toString()); + setHeaderNewsId(); + CLIENT.put(getAbsoluteApiUrl(partUrl), handler); + log("PUT " + partUrl); } public static void put(String partUrl, RequestParams params, AsyncHttpResponseHandler handler) { - client.put(getAbsoluteApiUrl(partUrl), params, handler); - log(new StringBuilder("PUT ").append(partUrl).append("&") - .append(params).toString()); + setHeaderNewsId(); + CLIENT.put(getAbsoluteApiUrl(partUrl), params, handler); + log("PUT " + partUrl + "?" + params); } - public static void setApiUrl(String apiUrl) { - API_URL = apiUrl; - } + private static String sessionKey = ""; public static void setHttpClient(AsyncHttpClient c) { - client = c; - client.addHeader("Accept-Language", Locale.getDefault().toString()); - client.addHeader("Host", HOST); - client.addHeader("Connection", "Keep-Alive"); - client.getHttpClient().getParams() + c.addHeader("Accept-Language", Locale.getDefault().toString()); + c.addHeader("Host", HOST); + c.addHeader("Connection", "Keep-Alive"); + if (TextUtils.isEmpty(sessionKey)) { + sessionKey = MD5.get32MD5Str(OSCSharedPreference.getInstance().getDeviceUUID() + System.currentTimeMillis()); + } + c.addHeader("sessionKey", sessionKey); + c.addHeader("uuid", OSCSharedPreference.getInstance().getDeviceUUID()); + + c.addHeader("Accept", "image/webp"); + //noinspection deprecation + c.getHttpClient().getParams() .setParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true); + // Set AppToken + //c.addHeader("AppToken", Verifier.getPrivateToken(application)); + c.addHeader("AppToken", APIVerify.getVerifyString()); + //c.addHeader("AppToken", "123456"); + // setUserAgent + c.setUserAgent(ApiClientHelper.getUserAgent(AppContext.getInstance())); + CLIENT = c; + setHeaderNewsId(); + initSSL(CLIENT); + initSSL(API.mClient); + } - setUserAgent(ApiClientHelper.getUserAgent(AppContext.getInstance())); + public static void setCookieHeader(String cookie) { + if (!TextUtils.isEmpty(cookie)) + CLIENT.addHeader("Cookie", cookie); + log("setCookieHeader:" + cookie); } - public static void setUserAgent(String userAgent) { - client.setUserAgent(userAgent); + /** + * 销毁当前AsyncHttpClient 并重新初始化网络参数,初始化Cookie等信息 + * + * @param appContext AppContext + */ + public static void destroyAndRestore(Application appContext) { + cleanCookie(); + CLIENT = null; + init(appContext); } - public static void setCookie(String cookie) { - client.addHeader("Cookie", cookie); + public static void cleanCookie() { + // first clear store + // new PersistentCookieStore(AppContext.getInstance()).clear(); + // clear header + AsyncHttpClient client = CLIENT; + if (client != null) { + HttpContext httpContext = client.getHttpContext(); + CookieStore cookies = (CookieStore) httpContext + .getAttribute(HttpClientContext.COOKIE_STORE); + // 清理Async本地存储 + if (cookies != null) { + cookies.clear(); + } + // 清理当前正在使用的Cookie + client.removeHeader("Cookie"); + } + log("cleanCookie"); } - private static String appCookie; + /** + * 从AsyncHttpClient自带缓存中获取CookieString + * + * @param client AsyncHttpClient + * @return CookieString + */ + private static String getClientCookie(AsyncHttpClient client) { + String cookie = ""; + if (client != null) { + HttpContext httpContext = client.getHttpContext(); + CookieStore cookies = (CookieStore) httpContext + .getAttribute(HttpClientContext.COOKIE_STORE); - public static void cleanCookie() { - appCookie = ""; + if (cookies != null && cookies.getCookies() != null && cookies.getCookies().size() > 0) { + for (Cookie c : cookies.getCookies()) { + cookie += (c.getName() + "=" + c.getValue()) + ";"; + } + } + } + log("getClientCookie:" + cookie); + return cookie; + } + + /** + * 得到当前的网络请求Cookie, + * 登录后触发 + * + * @param headers Header + */ + public static String getCookie(Header[] headers) { + String cookie = getClientCookie(ApiHttpClient.getHttpClient()); + if (TextUtils.isEmpty(cookie)) { + cookie = ""; + if (headers != null) { + for (Header header : headers) { + String key = header.getName(); + String value = header.getValue(); + if (key.contains("Set-Cookie")) + cookie += value + ";"; + } + if (cookie.length() > 0) { + cookie = cookie.substring(0, cookie.length() - 1); + } + } + } + + log("getCookie:" + cookie); + return cookie; } - public static String getCookie(AppContext appContext) { - if (appCookie == null || appCookie == "") { - appCookie = appContext.getProperty("cookie"); + private static void initSSL(AsyncHttpClient client) { + try { + /// We initialize a default Keystore + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + // We load the KeyStore + trustStore.load(null, null); + // We initialize a new SSLSocketFacrory + MySSLSocketFactory socketFactory = new MySSLSocketFactory(trustStore); + // We set that all host names are allowed in the socket factory + socketFactory.setHostnameVerifier(MySSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + // We set the SSL Factory + client.setSSLSocketFactory(socketFactory); + // We initialize a GET http request + } catch (Exception e) { + e.printStackTrace(); + } + } + + @SuppressWarnings("deprecation") + private static class MySSLSocketFactory extends SSLSocketFactory { + SSLContext sslContext = SSLContext.getInstance("TLS"); + + @SuppressWarnings("WeakerAccess") + public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { + super(truststore); + + TrustManager tm = new X509TrustManager() { + @SuppressLint("TrustAllX509TrustManager") + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + @SuppressLint("TrustAllX509TrustManager") + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return null; + } + }; + + sslContext.init(null, new TrustManager[]{tm}, null); + } + + @Override + public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { + return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); + } + + @Override + public Socket createSocket() throws IOException { + return sslContext.getSocketFactory().createSocket(); } - return appCookie; } } diff --git a/app/src/main/java/net/oschina/app/api/ApiResponse.java b/app/src/main/java/net/oschina/app/api/ApiResponse.java deleted file mode 100644 index 119d9af584fa5d1a703fee3af0bb831b56ab6217..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/api/ApiResponse.java +++ /dev/null @@ -1,90 +0,0 @@ -package net.oschina.app.api; - -import org.json.JSONObject; - -public class ApiResponse { - protected Object _data; - protected String _message; - protected int _errorCode; - protected boolean _isOk; - private long _total; - private String _serverTime; - private boolean isCanceled; - - public ApiResponse(JSONObject json) { - if (json != null) { - setData(json.optJSONObject("data") == null ? json - .optJSONArray("data") : json.optJSONObject("data")); - setMessage(json.optString("result_desc")); - setErrorCode(json.optInt("result_code")); - setOk(getErrorCode() == 0); - setServerTime(json.optString("timestamp")); - } - } - - public Object getData() { - return _data; - } - - public void setData(Object _data) { - this._data = _data; - } - - public boolean isOk() { - return _isOk; - } - - public void setOk(boolean _isOk) { - this._isOk = _isOk; - } - - public String getMessage() { - return _message; - } - - public void setMessage(String _message) { - this._message = _message; - } - - public int getErrorCode() { - return _errorCode; - } - - public void setErrorCode(int _errorCode) { - this._errorCode = _errorCode; - } - - @Override - public String toString() { - return "data:" + getData() + " message:" + getMessage() + " errocode:" - + _errorCode; - } - - public void update(ApiResponse response) { - _message = response.getMessage(); - } - - public void setTotal(long total) { - _total = total; - } - - public long getTotal() { - return _total; - } - - public String getServerTime() { - return _serverTime; - } - - public void setServerTime(String _serverTime) { - this._serverTime = _serverTime; - } - - public boolean isCanceled() { - return isCanceled; - } - - public void setCanceled(boolean isCanceled) { - this.isCanceled = isCanceled; - } -} diff --git a/app/src/main/java/net/oschina/app/api/OperationResponseHandler.java b/app/src/main/java/net/oschina/app/api/OperationResponseHandler.java index 604fdca13259aaea817a747d3ad98c12e09703e9..cf8f428f0a15dfad786e83053dc77349d88a2799 100644 --- a/app/src/main/java/net/oschina/app/api/OperationResponseHandler.java +++ b/app/src/main/java/net/oschina/app/api/OperationResponseHandler.java @@ -1,46 +1,46 @@ package net.oschina.app.api; -import java.io.ByteArrayInputStream; - - import android.os.Looper; -import cz.msebera.android.httpclient.Header; import com.loopj.android.http.AsyncHttpResponseHandler; +import java.io.ByteArrayInputStream; + +import cz.msebera.android.httpclient.Header; + public class OperationResponseHandler extends AsyncHttpResponseHandler { - private Object[] args; + private Object[] args; - public OperationResponseHandler(Looper looper, Object... args) { - super(looper); - this.args = args; - } + public OperationResponseHandler(Looper looper, Object... args) { + super(looper); + this.args = args; + } - public OperationResponseHandler(Object... args) { - this.args = args; - } + public OperationResponseHandler(Object... args) { + this.args = args; + } - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, Throwable arg3) { - onFailure(arg0, arg3.getMessage(), args); - } + @Override + public void onFailure(int arg0, Header[] arg1, byte[] arg2, Throwable arg3) { + onFailure(arg0, arg3.getMessage(), args); + } - public void onFailure(int code, String errorMessage, Object[] args) { - } + public void onFailure(int code, String errorMessage, Object[] args) { + } - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - try { - onSuccess(arg0, new ByteArrayInputStream(arg2), args); - } catch (Exception e) { - e.printStackTrace(); - onFailure(arg0, e.getMessage(), args); - } - } + @Override + public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { + try { + onSuccess(arg0, new ByteArrayInputStream(arg2), args); + } catch (Exception e) { + e.printStackTrace(); + onFailure(arg0, e.getMessage(), args); + } + } - public void onSuccess(int code, ByteArrayInputStream is, Object[] args) - throws Exception { + public void onSuccess(int code, ByteArrayInputStream is, Object[] args) + throws Exception { - } + } } diff --git a/app/src/main/java/net/oschina/app/api/remote/OSChinaApi.java b/app/src/main/java/net/oschina/app/api/remote/OSChinaApi.java index cc5097959e8b65cb2ab05b106b1c4143db18d645..3a0d6c177aeb9d26f1abc8e72d5ad51dfa07eb09 100644 --- a/app/src/main/java/net/oschina/app/api/remote/OSChinaApi.java +++ b/app/src/main/java/net/oschina/app/api/remote/OSChinaApi.java @@ -1,178 +1,130 @@ package net.oschina.app.api.remote; +import android.content.Context; import android.text.TextUtils; +import android.util.Log; +import com.loopj.android.http.AsyncHttpClient; import com.loopj.android.http.AsyncHttpResponseHandler; import com.loopj.android.http.RequestParams; +import com.loopj.android.http.TextHttpResponseHandler; import net.oschina.app.AppContext; -import net.oschina.app.AppException; import net.oschina.app.api.ApiHttpClient; import net.oschina.app.bean.EventApplyData; -import net.oschina.app.bean.NewsList; import net.oschina.app.bean.Report; -import net.oschina.app.bean.Tweet; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.bean.SignUpEventOptions; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.detail.sign.StringParams; +import net.oschina.app.improve.tweet.service.YouPaiResult; +import net.oschina.app.improve.write.Blog; import net.oschina.app.team.bean.Team; import net.oschina.app.util.StringUtils; -import net.oschina.app.util.TLog; - -import org.kymjs.kjframe.utils.KJLoger; import java.io.File; import java.io.FileNotFoundException; -import java.net.URLEncoder; +import java.util.List; +import java.util.Map; -public class OSChinaApi { +import static net.oschina.app.api.ApiHttpClient.post; - /** - * 登陆 - * - * @param username - * @param password - * @param handler - */ - public static void login(String username, String password, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("username", username); - params.put("pwd", password); - params.put("keep_login", 1); - String loginurl = "action/api/login_validate"; - ApiHttpClient.post(loginurl, params, handler); - } +/** + * OSChina Api v1 and v2 + */ +@SuppressWarnings("all") +public class OSChinaApi { - public static void openIdLogin(String s) { + public static final int CATALOG_ALL = 0; + public static final int CATALOG_SOFTWARE = 1; + public static final int CATALOG_QUESTION = 2; + public static final int CATALOG_BLOG = 3; + public static final int CATALOG_TRANSLATION = 4; + public static final int CATALOG_EVENT = 5; + public static final int CATALOG_NEWS = 6; + public static final int CATALOG_TWEET = 100; - } + public static final int COMMENT_SOFT = 1; // 软件推荐-不支持(默认软件评论其实是动弹) + public static final int COMMENT_QUESTION = 2; // 讨论区帖子 + public static final int COMMENT_BLOG = 3; // 博客 + public static final int COMMENT_TRANSLATION = 4; // 翻译文章 + public static final int COMMENT_EVENT = 5; // 活动类型 + public static final int COMMENT_NEWS = 6; // 资讯类型 + public static final int COMMENT_TWEET = 100; // 动弹 - /** - * 获取新闻列表 - * - * @param catalog - * 类别 (1,2,3) - * @param page - * 第几页 - * @param handler - */ - public static void getNewsList(int catalog, int page, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("catalog", catalog); - params.put("pageIndex", page); - params.put("pageSize", AppContext.PAGE_SIZE); - if (catalog == NewsList.CATALOG_WEEK) { - params.put("show", "week"); - } else if (catalog == NewsList.CATALOG_MONTH) { - params.put("show", "month"); - } - ApiHttpClient.get("action/api/news_list", params, handler); - } + public static final int COMMENT_HOT_ORDER = 2; //热门评论顺序 + public static final int COMMENT_NEW_ORDER = 1; //最新评论顺序 - public static void getBlogList(String type, int pageIndex, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("type", type); - params.put("pageIndex", pageIndex); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/blog_list", params, handler); - } + public static final int CATALOG_BANNER_NEWS = 1; // 资讯Banner + public static final int CATALOG_BANNER_BLOG = 2; // 博客Banner + public static final int CATALOG_BANNER_EVENT = 3; // 活动Banner - public static void getPostList(int catalog, int page, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("catalog", catalog); - params.put("pageIndex", page); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/post_list", params, handler); - } + public static final int CATALOG_BLOG_NORMAL = 1; // 最新 + public static final int CATALOG_BLOG_HEAT = 2; // 最热 + public static final int CATALOG_BLOG_RECOMMEND = 3; //推荐 - public static void getPostListByTag(String tag, int page, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("tag", tag); - params.put("pageIndex", page); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/post_list", params, handler); - } + public static final String CATALOG_NEWS_DETAIL = "news"; + public static final String CATALOG_TRANSLATE_DETAIL = "translation"; + public static final String CATALOG_SOFTWARE_DETAIL = "software"; - public static void getTweetList(int uid, int page, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("pageIndex", page); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/tweet_list", params, handler); - } + public static final String LOGIN_WEIBO = "weibo"; + public static final String LOGIN_QQ = "qq"; + public static final String LOGIN_WECHAT = "wechat"; + public static final String LOGIN_CSDN = "csdn"; - public static void getTweetTopicList(int page, String topic, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("pageIndex", page); - params.put("title", topic); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/tweet_topic_list", params, handler); - } + public static final int REGISTER_INTENT = 1; + public static final int RESET_PWD_INTENT = 2; - public static void getTweetLikeList(AsyncHttpResponseHandler handler) { - ApiHttpClient.get("action/api/my_tweet_like_list", handler); - } + public static final int REQUEST_COUNT = 0x50;//请求分页大小 - public static void pubLikeTweet(int tweetId, int authorId, - AsyncHttpResponseHandler handler) { + public static final int TYPE_USER_FLOWS = 1;//你关注的人 + public static final int TYPE_USER_FANS = 2;//关注你的人 - RequestParams params = new RequestParams(); - params.put("tweetid", tweetId); - params.put("uid", AppContext.getInstance().getLoginUid()); - params.put("ownerOfTweet", authorId); - ApiHttpClient.post("action/api/tweet_like", params, handler); - } - public static void pubUnLikeTweet(int tweetId, int authorId, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("tweetid", tweetId); - params.put("uid", AppContext.getInstance().getLoginUid()); - params.put("ownerOfTweet", authorId); - ApiHttpClient.post("action/api/tweet_unlike", params, handler); - } + /* ============================================================================================= + * ============================================================================================= + * + * Oschina Api V1 + * Don't use them any more + * + * ============================================================================================= + * ============================================================================================= + */ - public static void getTweetLikeList(int tweetId, int page, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void getPostListByTag(String tag, int page, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); - params.put("tweetid", tweetId); + params.put("tag", tag); params.put("pageIndex", page); params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/tweet_like_list", params, handler); - + ApiHttpClient.get("action/api/post_list", params, handler); } - public static void getActiveList(int uid, int catalog, int page, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void pubLikeTweet(int tweetId, int authorId, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("catalog", catalog); - params.put("pageIndex", page); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/active_list", params, handler); + params.put("tweetid", tweetId); + params.put("uid", AccountHelper.getUserId()); + params.put("ownerOfTweet", authorId); + post("action/api/tweet_like", params, handler); } - public static void getFriendList(int uid, int relation, int page, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void pubUnLikeTweet(int tweetId, int authorId, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("relation", relation); - params.put("pageIndex", page); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/friends_list", params, handler); + params.put("tweetid", tweetId); + params.put("uid", AccountHelper.getUserId()); + params.put("ownerOfTweet", authorId); + post("action/api/tweet_unlike", params, handler); } /** * 获取所有关注好友列表 * - * @param uid - * 指定用户UID + * @param uid 指定用户UID * @param handler - * */ + */ + @Deprecated public static void getAllFriendsList(int uid, int relation, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("uid", uid); @@ -181,41 +133,21 @@ public class OSChinaApi { ApiHttpClient.get("action/api/friends_list", params, handler); } - /** - * 获取用户收藏 - * - * @param uid - * 指定用户UID - * @param type - * 收藏类型: 0:全部收藏 1:软件 2:话题 3:博客 4:新闻 5:代码 - * @param page - * @param handler - */ - public static void getFavoriteList(int uid, int type, int page, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("type", type); - params.put("pageIndex", page); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/favorite_list", params, handler); - } - /** * 分类列表 * - * @param tag - * 第一级:0 + * @param tag 第一级:0 * @param handler */ - public static void getSoftwareCatalogList(int tag, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void getSoftwareCatalogList(int tag, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams("tag", tag); ApiHttpClient.get("action/api/softwarecatalog_list", params, handler); } + @Deprecated public static void getSoftwareTagList(int searchTag, int page, - AsyncHttpResponseHandler handler) { + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("searchTag", searchTag); params.put("pageIndex", page); @@ -224,13 +156,13 @@ public class OSChinaApi { } /** - * @param searchTag - *   软件分类  推荐:recommend 最新:time 热门:view 国产:list_cn + * @param searchTag   软件分类  推荐:recommend 最新:time 热门:view 国产:list_cn * @param page * @param handler */ + @Deprecated public static void getSoftwareList(String searchTag, int page, - AsyncHttpResponseHandler handler) { + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("searchTag", searchTag); params.put("pageIndex", page); @@ -243,12 +175,13 @@ public class OSChinaApi { * * @PARAM ID * @PARAM CATALOG - * 1新闻 2帖子 3动弹 4动态 + * 1新闻 2帖子 3动弹 4动态 * @PARAM PAGE * @PARAM HANDLER */ - public static void getCommentList(int id, int catalog, int page, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void getCommentList(int id, int catalog, int page + , AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("catalog", catalog); params.put("id", id); @@ -258,8 +191,8 @@ public class OSChinaApi { ApiHttpClient.get("action/api/comment_list", params, handler); } - public static void getBlogCommentList(int id, int page, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void getBlogCommentList(int id, int page, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("id", id); params.put("pageIndex", page); @@ -267,132 +200,31 @@ public class OSChinaApi { ApiHttpClient.get("action/api/blogcomment_list", params, handler); } - public static void getChatMessageList(int friendId, int page, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("id", friendId); - params.put("pageIndex", page); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/message_detail", params, handler); - } - - public static void getUserInformation(int uid, int hisuid, String hisname, - int pageIndex, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("hisuid", hisuid); - params.put("hisname", hisname); - params.put("pageIndex", pageIndex); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/user_information", params, handler); - } - - @SuppressWarnings("deprecation") - public static void getUserBlogList(int authoruid, final String authorname, - final int uid, final int pageIndex, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("authoruid", authoruid); - params.put("authorname", URLEncoder.encode(authorname)); - params.put("uid", uid); - params.put("pageIndex", pageIndex); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/userblog_list", params, handler); - } - - public static void updateRelation(int uid, int hisuid, int newrelation, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("hisuid", hisuid); - params.put("newrelation", newrelation); - ApiHttpClient.post("action/api/user_updaterelation", params, handler); - } - - public static void getMyInformation(int uid, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", uid); - ApiHttpClient.get("action/api/my_information", params, handler); - } - - /** - * 获取新闻明细 - * - * @param id 新闻的id - * @param handler - */ - public static void getNewsDetail(int id, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams("id", id); - ApiHttpClient.get("action/api/news_detail", params, handler); - } - - public static void getBlogDetail(int id, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams("id", id); - ApiHttpClient.get("action/api/blog_detail", params, handler); - } - - /** - * 获取软件详情 - * - * @param ident - * @param handler - */ - public static void getSoftwareDetail(String ident, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams("ident", - ident); - ApiHttpClient.get("action/api/software_detail", params, handler); - } - - /*** - * 通过id获取软件详情 - * @param id - * @param handler - */ - public static void getSoftwareDetail(int id, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams("id", - id); - ApiHttpClient.get("action/api/software_detail", params, handler); - } - - public static void getPostDetail(int id, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams("id", id); - ApiHttpClient.get("action/api/post_detail", params, handler); - } - - public static void getTweetDetail(int id, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams("id", id); - ApiHttpClient.get("action/api/tweet_detail", params, handler); - } - /** * 用户针对某个新闻,帖子,动弹,消息发表评论的接口,参数使用POST方式提交 * - * @param catalog - *    1新闻  2 帖子  3 动弹  4消息中心 - * @param id - * 被评论的某条新闻,帖子,动弹或者某条消息的id - * @param uid - * 当天登陆用户的UID - * @param content - * 发表的评论内容 - * @param isPostToMyZone - * 是否转发到我的空间,0不转发  1转发到我的空间(注意该功能之对某条动弹进行评论是有效,其他情况下服务器借口可以忽略该参数) + * @param catalog    1新闻  2 帖子  3 动弹  4消息中心 + * @param id 被评论的某条新闻,帖子,动弹或者某条消息的id + * @param uid 当天登陆用户的UID + * @param content 发表的评论内容 + * @param isPostToMyZone 是否转发到我的空间,0不转发  1转发到我的空间(注意该功能之对某条动弹进行评论是有效,其他情况下服务器借口可以忽略该参数) * @param handler */ - public static void publicComment(int catalog, int id, int uid, - String content, int isPostToMyZone, AsyncHttpResponseHandler handler) { + @Deprecated + public static void publicComment(int catalog, long id, int uid, String content + , int isPostToMyZone, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("catalog", catalog); params.put("id", id); params.put("uid", uid); params.put("content", content); params.put("isPostToMyZone", isPostToMyZone); - ApiHttpClient.post("action/api/comment_pub", params, handler); + post("action/api/comment_pub", params, handler); } - public static void replyComment(int id, int catalog, int replyid, - int authorid, int uid, String content, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void replyComment(int id, int catalog, int replyid, int authorid + , int uid, String content, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("catalog", catalog); params.put("id", id); @@ -400,250 +232,104 @@ public class OSChinaApi { params.put("content", content); params.put("replyid", replyid); params.put("authorid", authorid); - ApiHttpClient.post("action/api/comment_reply", params, handler); + post("action/api/comment_reply", params, handler); } - public static void publicBlogComment(int blog, int uid, String content, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void publicBlogComment(long blog, int uid, String content + , AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("blog", blog); params.put("uid", uid); params.put("content", content); - ApiHttpClient.post("action/api/blogcomment_pub", params, handler); + post("action/api/blogcomment_pub", params, handler); } - public static void replyBlogComment(int blog, int uid, String content, - int reply_id, int objuid, AsyncHttpResponseHandler handler) { + @Deprecated + public static void replyBlogComment(long blog, long uid, String content, long reply_id + , long objuid, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("blog", blog); params.put("uid", uid); params.put("content", content); params.put("reply_id", reply_id); params.put("objuid", objuid); - ApiHttpClient.post("action/api/blogcomment_pub", params, handler); - } - - public static void pubTweet(Tweet tweet, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", tweet.getAuthorid()); - params.put("msg", tweet.getBody()); - - // Map files = new HashMap(); - if (!TextUtils.isEmpty(tweet.getImageFilePath())) { - try { - params.put("img", new File(tweet.getImageFilePath())); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - } - if (!TextUtils.isEmpty(tweet.getAudioPath())) { - try { - params.put("amr", new File(tweet.getAudioPath())); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - } - ApiHttpClient.post("action/api/tweet_pub", params, handler); - } - - public static void pubSoftWareTweet(Tweet tweet, int softid, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", tweet.getAuthorid()); - params.put("msg", tweet.getBody()); - params.put("project", softid); - ApiHttpClient.post("action/api/software_tweet_pub", params, handler); + post("action/api/blogcomment_pub", params, handler); } - public static void deleteTweet(int uid, int tweetid, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void deleteTweet(int uid, int tweetid, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("uid", uid); params.put("tweetid", tweetid); - ApiHttpClient.post("action/api/tweet_delete", params, handler); + post("action/api/tweet_delete", params, handler); } + @Deprecated public static void deleteComment(int id, int catalog, int replyid, - int authorid, AsyncHttpResponseHandler handler) { + int authorid, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("id", id); params.put("catalog", catalog); params.put("replyid", replyid); params.put("authorid", authorid); - ApiHttpClient.post("action/api/comment_delete", params, handler); - } - - public static void deleteBlog(int uid, int authoruid, int id, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("authoruid", authoruid); - params.put("id", id); - ApiHttpClient.post("action/api/userblog_delete", params, handler); + post("action/api/comment_delete", params, handler); } - public static void deleteBlogComment(int uid, int blogid, int replyid, - int authorid, int owneruid, AsyncHttpResponseHandler handler) { + @Deprecated + public static void deleteBlogComment(int uid, int blogid, int replyid, int authorid + , int owneruid, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("uid", uid); params.put("blogid", blogid); params.put("replyid", replyid); params.put("authorid", authorid); params.put("owneruid", owneruid); - ApiHttpClient.post("action/api/blogcomment_delete", params, handler); + post("action/api/blogcomment_delete", params, handler); } /** * 用户添加收藏 * - * @param uid - * 用户UID - * @param objid - * 比如是新闻ID 或者问答ID 或者动弹ID - * @param type - * 1:软件 2:话题 3:博客 4:新闻 5:代码 + * @param uid 用户UID + * @param objid 比如是新闻ID 或者问答ID 或者动弹ID + * @param type 1:软件 2:话题 3:博客 4:新闻 5:代码 */ - public static void addFavorite(int uid, int objid, int type, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void addFavorite(int uid, long objid, int type, + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("uid", uid); params.put("objid", objid); params.put("type", type); - ApiHttpClient.post("action/api/favorite_add", params, handler); + post("action/api/favorite_add", params, handler); } - public static void delFavorite(int uid, int objid, int type, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void delFavorite(int uid, long objid, int type, + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("uid", uid); params.put("objid", objid); params.put("type", type); - ApiHttpClient.post("action/api/favorite_delete", params, handler); - } - - public static void getSearchList(String catalog, String content, - int pageIndex, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("catalog", catalog); - params.put("content", content); - params.put("pageIndex", pageIndex); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/search_list", params, handler); - } - - public static void publicMessage(int uid, int receiver, String content, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("receiver", receiver); - params.put("content", content); - - ApiHttpClient.post("action/api/message_pub", params, handler); - } - - public static void deleteMessage(int uid, int friendid, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("friendid", friendid); - ApiHttpClient.post("action/api/message_delete", params, handler); - } - - public static void forwardMessage(int uid, String receiverName, - String content, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("receiverName", receiverName); - params.put("content", content); - ApiHttpClient.post("action/api/message_pub", params, handler); - } - - public static void getMessageList(int uid, int pageIndex, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("pageIndex", pageIndex); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/message_list", params, handler); - } - - public static void updatePortrait(int uid, File portrait, - AsyncHttpResponseHandler handler) throws FileNotFoundException { - RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("portrait", portrait); - ApiHttpClient.post("action/api/portrait_update", params, handler); - } - - public static void getNotices(AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", AppContext.getInstance().getLoginUid()); - ApiHttpClient.get("action/api/user_notice", params, handler); - } - - /** - * 清空通知消息 - * - * @param uid - * @param type - * 1:@我的信息 2:未读消息 3:评论个数 4:新粉丝个数 - * @return - * @throws AppException - */ - public static void clearNotice(int uid, int type, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("type", type); - ApiHttpClient.post("action/api/notice_clear", params, handler); + post("action/api/favorite_delete", params, handler); } - public static void singnIn(String url, AsyncHttpResponseHandler handler) { + @Deprecated + public static void signin(String url, AsyncHttpResponseHandler handler) { ApiHttpClient.getDirect(url, handler); } - /** - * 获取软件的动态列表 - * - * @param softid - * @param handler - */ - public static void getSoftTweetList(int softid, int page, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("project", softid); - params.put("pageIndex", page); - params.put("pageSize", AppContext.PAGE_SIZE); - ApiHttpClient.get("action/api/software_tweet_list", params, handler); - } - - public static void checkUpdate(AsyncHttpResponseHandler handler) { - ApiHttpClient.get("MobileAppVersion.xml", handler); - } - - /** - * 查找用户 - * - * @param username - * @param handler - */ - public static void findUser(String username, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("name", username); - ApiHttpClient.get("action/api/find_user", params, handler); - } - /** * 获取活动列表 - * + * * @param pageIndex - * @param uid - * <= 0 近期活动 实际的用户ID 则获取用户参与的活动列表,需要已登陆的用户 + * @param uid <= 0 近期活动 实际的用户ID 则获取用户参与的活动列表,需要已登陆的用户 * @param handler */ + @Deprecated public static void getEventList(int pageIndex, int uid, - AsyncHttpResponseHandler handler) { + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("pageIndex", pageIndex); params.put("uid", uid); @@ -653,13 +339,14 @@ public class OSChinaApi { /** * 获取某活动已出席的人员列表 - * + * * @param eventId * @param pageIndex * @param handler */ + @Deprecated public static void getEventApplies(int eventId, int pageIndex, - AsyncHttpResponseHandler handler) { + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("pageIndex", pageIndex); params.put("event_id", eventId); @@ -669,10 +356,11 @@ public class OSChinaApi { /** * 举报 - * + * * @param report * @param handler */ + @Deprecated public static void report(Report report, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("obj_id", report.getObjId()); @@ -683,37 +371,17 @@ public class OSChinaApi { && !StringUtils.isEmpty(report.getOtherReason())) { params.put("memo", report.getOtherReason()); } - ApiHttpClient.post("action/communityManage/report", params, handler); - } - - /** - * 摇一摇,随机数据 - * - * @param handler - */ - public static void shake(AsyncHttpResponseHandler handler) { - shake(-1, handler); - } - - /** - * 摇一摇指定请求类型 - */ - public static void shake(int type, AsyncHttpResponseHandler handler) { - String inter = "action/api/rock_rock"; - if (type > 0) { - inter = (inter + "/?type=" + type); - } - ApiHttpClient.get(inter, handler); + post("action/communityManage/report", params, handler); } /** * 活动报名 - * + * * @param data * @param handler */ - public static void eventApply(EventApplyData data, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void eventApply(EventApplyData data, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("event", data.getEvent()); params.put("user", data.getUser()); @@ -725,47 +393,19 @@ public class OSChinaApi { if (!StringUtils.isEmpty(data.getRemark())) { params.put("misc_info", data.getRemark()); } - ApiHttpClient.post("action/api/event_apply", params, handler); - } - - private static void uploadLog(String data, String report, - AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("app", "1"); - params.put("report", report); - params.put("msg", data); - ApiHttpClient.post("action/api/user_report_to_admin", params, handler); - } - - /** - * BUG上报 - * - * @param data - * @param handler - */ - public static void uploadLog(String data, AsyncHttpResponseHandler handler) { - uploadLog(data, "1", handler); - } - - /** - * 反馈意见 - * - * @param data - * @param handler - */ - public static void feedback(String data, AsyncHttpResponseHandler handler) { - uploadLog(data, "2", handler); + post("action/api/event_apply", params, handler); } /** * team动态 - * + * * @param team * @param page * @param handler */ + @Deprecated public static void teamDynamic(Team team, int page, - AsyncHttpResponseHandler handler) { + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); // int uid = AppContext.getInstance().getLoginUid(); // params.put("uid", uid); @@ -778,47 +418,35 @@ public class OSChinaApi { /** * 获取team列表 - * + * * @param handler */ - public static void teamList(AsyncHttpResponseHandler handler) { + @Deprecated + public static void teamList(AsyncHttpResponseHandler handler, Context context) { RequestParams params = new RequestParams(); - params.put("uid", AppContext.getInstance().getLoginUid()); + params.put("uid", AccountHelper.getUserId()); ApiHttpClient.get("action/api/team_list", params, handler); } /** * 获取team成员列表 - * + * * @param handler */ + @Deprecated public static void getTeamMemberList(int teamid, - AsyncHttpResponseHandler handler) { + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamid); ApiHttpClient.get("action/api/team_member_list", params, handler); } - /** - * 获取team成员个人信息 - * - * @param handler - */ - public static void getTeamUserInfo(String teamid, String uid, - int pageIndex, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("teamid", teamid); - params.put("uid", uid); - params.put("pageIndex", pageIndex); - params.put("pageSize", 20); - ApiHttpClient.get("action/api/team_user_information", params, handler); - } - /** * 获取我的任务中进行中、未完成、已完成等状态的数量 */ + @Deprecated public static void getMyIssueState(String teamid, String uid, - AsyncHttpResponseHandler handler) { + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamid); params.put("uid", uid); @@ -829,27 +457,29 @@ public class OSChinaApi { /** * 获取指定用户的动态 */ + @Deprecated public static void getUserDynamic(int teamid, String uid, int pageIndex, - AsyncHttpResponseHandler handler) { + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamid); params.put("pageIndex", pageIndex); params.put("pageSize", 20); - params.put("type", "git"); + params.put("type", "all"); params.put("uid", uid); ApiHttpClient.get("action/api/team_active_list", params, handler); } /** * 动态详情 - * + * * @param activeid * @param teamid * @param uid * @param handler */ - public static void getDynamicDetail(int activeid, int teamid, int uid, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void getDynamicDetail(int activeid, int teamid, int uid + , AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamid); params.put("uid", uid); @@ -861,7 +491,7 @@ public class OSChinaApi { * 获取指定用户的任务 */ public static void getMyIssue(String teamid, String uid, int pageIndex, - String type, AsyncHttpResponseHandler handler) { + String type, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamid); params.put("uid", uid); @@ -874,14 +504,15 @@ public class OSChinaApi { /** * 获取指定周周报 - * + * * @param teamid * @param year * @param week * @param handler */ - public static void getDiaryFromWhichWeek(int teamid, int year, int week, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void getDiaryFromWhichWeek(int teamid, int year, int week + , AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamid); params.put("year", year); @@ -891,14 +522,12 @@ public class OSChinaApi { /** * 删除一个便签 - * - * @param id - * 便签id - * @param uid - * 用户id + * + * @param id 便签id + * @param uid 用户id */ - public static void deleteNoteBook(int id, int uid, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void deleteNoteBook(int id, long uid, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("uid", uid); params.put("id", id); // 便签id @@ -906,21 +535,15 @@ public class OSChinaApi { .get("action/api/team_stickynote_recycle", params, handler); } - public static void getNoteBook(int uid, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", uid); - ApiHttpClient.get("action/api/team_sticky_list", params, handler); - } - /** * 获取指定周报的详细信息 - * + * * @param teamid * @param diaryid * @param handler */ - public static void getDiaryDetail(int teamid, int diaryid, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void getDiaryDetail(int teamid, int diaryid, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamid); params.put("diaryid", diaryid); @@ -929,101 +552,1677 @@ public class OSChinaApi { /** * diary评论列表 - * + * * @param teamid * @param diaryid * @param handler */ - public static void getDiaryComment(int teamid, int diaryid, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void getDiaryComment(int teamid, int diaryid, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamid); params.put("id", diaryid); params.put("type", "diary"); params.put("pageIndex", 0); params.put("pageSize", "20"); - KJLoger.debug(teamid + "==getDiaryComment接口=" + diaryid); - ApiHttpClient - .get("action/api/team_reply_list_by_type", params, handler); - } - - /** - * 周报评论(以后可改为全局评论) - * - * @param uid - * @param teamid - * @param diaryId - * @param content - * @param handler - */ - public static void sendComment(int uid, int teamid, int diaryId, - String content, AsyncHttpResponseHandler handler) { - RequestParams params = new RequestParams(); - params.put("uid", uid); - params.put("teamid", teamid); - params.put("type", "118"); - params.put("tweetid", diaryId); - params.put("content", content); - ApiHttpClient.post("action/api/team_tweet_reply", params, handler); + ApiHttpClient.get("action/api/team_reply_list_by_type", params, handler); } /*** * 客户端扫描二维码登陆 - * - * @author 火蚁 2015-3-13 上午11:45:47 - * - * @return void + * * @param url * @param handler + * @return void + * @author 火蚁 2015-3-13 上午11:45:47 */ - public static void scanQrCodeLogin(String url, - AsyncHttpResponseHandler handler) { + @Deprecated + public static void scanQrCodeLogin(String url, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); String uuid = url.substring(url.lastIndexOf("=") + 1); params.put("uuid", uuid); ApiHttpClient.getDirect(url, handler); } - /*** - * 使用第三方登陆 - * @param catalog 类别 - * @param openIdInfo 第三方的info + + + + + /* ============================================================================================= + * ============================================================================================= + * + * Oschina Api V2 + * + * ============================================================================================= + * ============================================================================================= + */ + + /** + * pub software tweet + * + * @param content content * @param handler handler */ - public static void open_login(String catalog, String openIdInfo, AsyncHttpResponseHandler handler) { + public static void pubSoftwareTweet(String content, AsyncHttpResponseHandler handler) { + if (!TextUtils.isEmpty(content)) { + RequestParams params = new RequestParams(); + params.put("content", content); + post("/action/apiv2/tweet", params, handler); + } + } + + /** + * del software tweet + * + * @param sourceId tweet's id + * @param handler handler + */ + public static void delSoftwareTweet(long sourceId, AsyncHttpResponseHandler handler) { + if (sourceId <= 0) return; RequestParams params = new RequestParams(); - params.put("catalog", catalog); - params.put("openid_info", openIdInfo); - ApiHttpClient.post("action/api/openid_login", params, handler); + params.put("sourceId", sourceId); + post("/action/apiv2/tweet_delete", params, handler); + } - /*** - * 第三方登陆账号绑定 - * @param catalog 类别(QQ、wechat) - * @param openIdInfo 第三方info - * @param userName 用户名 - * @param pwd 密码 + /** + * get software tweet list + * + * @param tag software tag * @param handler handler */ - public static void bind_openid(String catalog, String openIdInfo, String userName, String pwd, AsyncHttpResponseHandler handler) { + public static void getSoftwareTweetList(String tag, String pageToken, TextHttpResponseHandler handler) { + if (TextUtils.isEmpty(tag)) return; RequestParams params = new RequestParams(); - params.put("catalog", catalog); - params.put("openid_info", openIdInfo); - params.put("username", userName); - params.put("pwd", pwd); - ApiHttpClient.post("action/api/openid_bind", params, handler); + params.put("tag", tag); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/tweets", params, handler); } - /*** - * 使用第三方账号注册 - * @param catalog 类别(qq、wechat) - * @param openIdInfo 第三方info + /** + * pub tweet like status + * + * @param sourceId source id + * @param handler handler + */ + public static void pubSoftwareLike(long sourceId, TextHttpResponseHandler handler) { + if (sourceId <= 0) return; + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + post("action/apiv2/tweet_like_reverse", params, handler); + } + + /** + * 请求资讯详情 + * + * @param id 请求该资讯详情页 + * @param handler AsyncHttpResponseHandler + */ + public static void getNewsDetail(long id, String type, AsyncHttpResponseHandler handler) { + if (id <= 0) return; + RequestParams params = new RequestParams(); + params.put("id", id); + ApiHttpClient.get("action/apiv2/" + type, params, handler); + } + + /** + * 请求资讯详情 [直接用软件名去请求] + * + * @param ident 请求该资讯详情页 + * @param handler AsyncHttpResponseHandler + */ + public static void getSoftwareDetail(String ident, String type, AsyncHttpResponseHandler handler) { + if (TextUtils.isEmpty(ident)) return; + RequestParams params = new RequestParams(); + params.put("ident", ident); + ApiHttpClient.get("action/apiv2/" + type, params, handler); + } + + /** + * 他人博客列表 + * + * @param pageToken page token + * @param uid user id + * @param uname user name + * @param handler handler + */ + public static void getSomeoneBlogs(String pageToken, long uid, String uname, + AsyncHttpResponseHandler handler) { + if (uid <= 0 && TextUtils.isEmpty(uname)) return; + RequestParams params = new RequestParams(); + params.put("pageToken", pageToken); + params.put("authorId", uid); + params.put("authorName", uname); + ApiHttpClient.get("action/apiv2/blog_list", params, handler); + } + + /** + * 请求用户自己的博客列表 + * + * @param pageToken 请求上下页数据令牌 + * @param handler AsyncHttpResponseHandler + */ + public static void getUserQuestionList(String pageToken, long userId, AsyncHttpResponseHandler handler) { + if (userId <= 0) return; + RequestParams params = new RequestParams(); + params.put("pageToken", pageToken); + params.put("authorId", userId); + ApiHttpClient.get("action/apiv2/question", params, handler); + } + + + /** + * 请求博客详情 + * + * @param id 博客id + * @param handler AsyncHttpResponseHandler + */ + public static void getBlogDetail(long id, AsyncHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("id", id); + ApiHttpClient.get("action/apiv2/blog", params, handler); + } + + /** + * 请求活动详情 + * + * @param id id * @param handler handler */ - public static void openid_reg(String catalog, String openIdInfo, AsyncHttpResponseHandler handler) { + public static void getEventDetail(long id, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); - params.put("catalog", catalog); - params.put("openid_info", openIdInfo); - ApiHttpClient.post("action/api/openid_reg", params, handler); + params.put("id", id); + ApiHttpClient.get("action/apiv2/event", params, handler); + } + + /** + * 更改收藏状态 + * + * @param id id + * @param type type + * @param handler handler + */ + public static void getFavReverse(long id, int type, AsyncHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("id", id); + params.put("type", type); + ApiHttpClient.get("action/apiv2/favorite_reverse", params, handler); + } + + /** + * 更改关注状态(关注/取消关注) + * + * @param id id + * @param handler handler + */ + public static void addUserRelationReverse(long id, AsyncHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("id", id); + ApiHttpClient.get("action/apiv2/user_relation_reverse", params, handler); + } + + /** + * 请求问答详情 + * + * @param id 问答id + * @param handler AsyncHttpResponseHandler + */ + public static void getQuestionDetail(long id, AsyncHttpResponseHandler handler) { + if (id <= 0) return; + RequestParams params = new RequestParams(); + params.put("id", id); + ApiHttpClient.get("action/apiv2/question", params, handler); + } + + /** + * 请求评论详情 + * + * @param id 评论Id + * @param handler AsyncHttpResponseHandler + */ + public static void getCommentDetail(long id, long aid, int type, TextHttpResponseHandler handler) { + if (id <= 0) return; + RequestParams params = new RequestParams(); + params.put("id", id); + params.put("authorId", aid); + params.put("type", type); + ApiHttpClient.get("action/apiv2/comment_detail", params, handler); + } + + /** + * 请求评论列表 + * + * @param sourceId 目标Id,该sourceId为资讯、博客、问答等文章的Id + * @param type 问答类型 {@link #COMMENT_SOFT, #COMMENT_QUESTION, #COMMENT_BLOG}, + * {@link #COMMENT_TRANSLATION, #COMMENT_EVENT, #COMMENT_NEWS} + * @param parts 请求的数据节点 parts="refer,reply" + * @param pageToken 请求上下页数据令牌 + * @param handler AsyncHttpResponseHandler + */ + @Deprecated + public static void getComments(long sourceId, int type, String parts, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + params.put("type", type); + params.put("parts", parts); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/comment", params, handler); + } + + + /** + * 请求评论列表(适用于所有的评论,但不包括软件评论列表,软件评论列表实际为动弹) + * + * @param sourceId sourceId 该sourceId为资讯、博客、问答等文章的Id + * @param type type 问答类型 {@link #COMMENT_SOFT, #COMMENT_QUESTION, #COMMENT_BLOG}, + * {@link #COMMENT_TRANSLATION, #COMMENT_EVENT, #COMMENT_NEWS} + * @param parts parts 请求的数据节点 parts="refer,reply" + * @param order order 请求的排序方式 1.最新 2.热门 + * @param pageToken pageToken 请求上下页数据令牌 + * @param handler handler + */ + public static void getComments(long sourceId, int type, String parts, int order, String pageToken, TextHttpResponseHandler handler) { + if (sourceId <= 0) return; + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + params.put("type", type); + params.put("parts", parts); + params.put("order", order); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/comment_list", params, handler); + } + + /** + * 发表评论 + * + * @param sourceId 文章id + * @param referId 引用的评论的id,问答评论详情 + * @param replyId 回复的评论的id + * @param reAuthorId 引用、回复的发布者id + * @param type 文章类型 1:软件推荐, 2:问答帖子, 3:博客, 4:翻译文章, 5:活动, 6:资讯 + * @param content 内容 + * @param handler 你懂得 + */ + public static void publishComment(long sourceId, long referId, long replyId, long reAuthorId, + int type, String content, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + params.put("type", type); + params.put("content", content); + if (referId > 0) + params.put("referId", referId); + if (replyId > 0) + params.put("replyId", replyId); + if (reAuthorId > 0) + params.put("reAuthorId", reAuthorId); + post("action/apiv2/comment_push", params, handler); + } + + /** + * 发布评论 + */ + public static void pubComment(long sourceId, int type, String content, long referId, long replyId, + long reAuthorId, + TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + params.put("type", type); + params.put("content", content); + if (referId > 0) + params.put("referId", referId); + if (replyId > 0) + params.put("replyId", replyId); + if (reAuthorId > 0) + params.put("reAuthorId", reAuthorId); + post("action/apiv2/comment_push", params, handler); + } + + /** + * 发表资讯评论 + * + * @see {{@link #publicComment(int, long, int, String, int, AsyncHttpResponseHandler)}} + */ + public static void pubNewsComment(long sid, long commentId, long commentAuthorId + , String comment, TextHttpResponseHandler handler) { + if (commentId == 0 || commentId == sid) { + commentId = 0; + commentAuthorId = 0; + } + publishComment(sid, 0, commentId, commentAuthorId, 6, comment, handler); + } + + /** + * 发表问答评论 + * + * @see {{@link #publicComment(int, long, int, String, int, AsyncHttpResponseHandler)}} + */ + public static void pubQuestionComment(long sid, long commentId, long commentAuthorId + , String comment, TextHttpResponseHandler handler) { + if (commentId == 0 || commentId == sid) { + commentId = 0; + commentAuthorId = 0; + } + publishComment(sid, 0, commentId, commentAuthorId, 2, comment, handler); + } + + + /** + * 发表翻译评论 + * + * @see {{@link #publicComment(int, long, int, String, int, AsyncHttpResponseHandler)}} + */ + public static void pubTranslateComment(long sid, long commentId, long commentAuthorId + , String comment, TextHttpResponseHandler handler) { + if (commentId == 0 || commentId == sid) { + commentId = 0; + commentAuthorId = 0; + } + publishComment(sid, 0, commentId, commentAuthorId, 4, comment, handler); + } + + /** + * 发布博客评论 + * + * @see {{@link #publicComment(int, long, int, String, int, AsyncHttpResponseHandler)}} + */ + public static void pubBlogComment(long sid, long commentId, long commentAuthorId + , String comment, TextHttpResponseHandler handler) { + if (commentId == 0 || commentId == sid) { + commentId = 0; + commentAuthorId = 0; + } + publishComment(sid, 0, commentId, commentAuthorId, 3, comment, handler); + } + + /** + * 发布活动评论 + * + * @param sid sourceId + * @param commentId commentId + * @param commentAuthorId commentAuthorId + * @param comment comment + * @param handler handler + */ + public static void pubEventComment(long sid, long commentId, long commentAuthorId + , String comment, TextHttpResponseHandler handler) { + if (commentId == 0 || commentId == sid) { + commentId = 0; + commentAuthorId = 0; + } + publishComment(sid, 0, commentId, commentAuthorId, 5, comment, handler); + } + + /** + * 发布软件评论 + * + * @param sid sourceId + * @param commentId commentId + * @param commentAuthorId commentAuthorId + * @param comment comment + * @param handler handler + */ + public static void pubSoftComment(long sid, long commentId, long commentAuthorId + , String comment, TextHttpResponseHandler handler) { + publishComment(sid, 0, commentId, commentAuthorId, 1, comment, handler); + } + + /** + * 对资讯,博客,翻译详情下的评论列表进行顶操作(ps:默认现在只能顶,不能取消) + * + * @param sourceType sourceType + * @param commentId commentId + * @param commentAuthorId commentAuthorId + * @param voteOpt voteOpt + * @param handler handler + */ + public static void voteComment(int sourceType, long commentId, long commentAuthorId + , int voteOpt, TextHttpResponseHandler handler) { + if (commentId <= 0) return; + RequestParams params = new RequestParams(); + params.put("sourceType", sourceType); + params.put("commentId", commentId); + params.put("commentAuthorId", commentAuthorId); + params.put("voteOpt", voteOpt); + post("action/apiv2/comment_vote_reverse", params, handler); + + } + + /** + * 问答的回答, 顶\踩 + * + * @param sid source id 问答的id + * @param cid 回答的id + * @param opt 操作类型 0:取消, 1:顶, 2:踩 + * @param handler + */ + public static void questionVote(long sid, long cid, int opt, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", sid); + params.put("commentId", cid); + params.put("voteOpt", opt); + post("action/apiv2/question_vote", params, handler); + } + + /** + * 上传图片接口 + * http://doc.oschina.net/app_v2?t=105508 + * + * @param token 上传口令,单次口令最多上传9张图片。 + * @param imagePath 图片地址 + * @param handler 回调 + */ + public static void uploadImage(String token, String imagePath, AsyncHttpResponseHandler handler) { + if (TextUtils.isEmpty(imagePath)) + throw new NullPointerException("imagePath is not null."); + RequestParams params = new RequestParams(); + params.put("token", token); + try { + params.put("resource", new File(imagePath)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + post("action/apiv2/resource_image", params, handler); + } + + + /** + * 上传图片接口 + * http://doc.oschina.net/app_v2?t=105508 + * + * @param token 上传口令,单次口令最多上传9张图片。 + * @param url 图片地址 + * @param handler 回调 + */ + public static void uploadImageForYouPai(String token, YouPaiResult result, AsyncHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("token", token); + String url = "http://oscimg.oschina.net/" + result.getUrl(); + Log.e("url", url); + params.put("url", url); + params.put("h", result.getImageHeight()); + params.put("w", result.getImageWidth()); + post("action/apiv2/resource_image", params, handler); + } + + + /** + * 获取又拍云上传策略 + * + * @param handler handler + */ + public static void getYPToken(AsyncHttpResponseHandler handler) { + ApiHttpClient.get("action/apiv2/get_upyun_token", handler); + } + + + /** + * 发布动弹 + * 链接 http://doc.oschina.net/app_v2?t=105522 + * + * @param content 内容 + * @param imagesToken 图片token + * @param audioToken 语音token + * @param share 相关分享节点,仅仅关注 {@link About.Share#id}, {@link About.Share#type}, + * {@link About.Share#fromTweetId} + * @param handler 回调 + */ + public static void pubTweet(String content, String imagesToken, String audioToken, About.Share share, AsyncHttpResponseHandler handler) { + if (TextUtils.isEmpty(content)) + throw new NullPointerException("content is not null."); + RequestParams params = new RequestParams(); + params.put("content", content); + params.put("images", imagesToken); + params.put("audio", audioToken); + if (About.check(share)) { + params.put("aboutId", share.id); + params.put("aboutType", share.type); + params.put("aboutFromTweetId", share.fromTweetId); + } + post("action/apiv2/tweet", params, handler); + } + + /** + * 请求用户动弹列表 + * + * @param authorId 用户id + * @param pageToken pageToken + * @param handler 回调 + */ + public static void getUserTweetList(long authorId, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("authorId", authorId); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/tweets", params, handler); + } + + /** + * 请求动弹详情 + * + * @param id 动弹id + * @param handler 回调 + */ + public static void getTweetDetail(long id, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("id", id); + ApiHttpClient.get("action/apiv2/tweet", params, handler); + } + + /** + * 请求动弹评论列表 + * + * @param sourceId 动弹id + * @param handler 回调 + */ + public static void getTweetCommentList(long sourceId, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/tweet_comments", params, handler); + } + + /** + * 请求动弹点赞列表 + * + * @param sourceId 动弹id + * @param handler 回调 + */ + public static void getTweetLikeList(long sourceId, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/tweet_likes", params, handler); + } + + /** + * 发表动弹评论列表 + * + * @param sourceId 动弹id + * @param content 内容 + * @param replyId 回复的用户id + * @param handler 回调 + */ + public static void pubTweetComment(long sourceId, String content, long replyId, AsyncHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + params.put("content", content); + if (replyId > 0) params.put("replyId", replyId); + post("action/apiv2/tweet_comment", params, handler); + } + + /** + * 更改动弹点赞状态 + * + * @param sourceId 动弹id + * @param handler 回调 + */ + public static void reverseTweetLike(long sourceId, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + ApiHttpClient.get("action/apiv2/tweet_like_reverse", params, handler); + } + + /** + * 删除动弹评论 + * + * @param sourceId 动弹id + * @param commentId 评论id + * @param handler 回调 + */ + public static void deleteTweetComment(long sourceId, long commentId, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + params.put("commentId", commentId); + ApiHttpClient.get("action/apiv2/tweet_comment_delete", params, handler); + } + + /** + * 删除动弹评论 + * + * @param sourceId 动弹id + * @param handler 回调 + */ + public static void deleteTweet(long sourceId, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + ApiHttpClient.get("action/apiv2/tweet_delete", params, handler); + } + + /** + * 获取消息列表 + * + * @param authorId authorId 用户id,不加该参数时返回所有给我发送消息的列表 + * @param pageToken pageToken + * @param handler 回调 + */ + public static void getMessageList(long authorId, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("authorId", authorId); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/messages", params, handler); + } + + /** + * 获取用户私信列表 + * + * @param pageToken pageToken + * @param handler 回调 + */ + public static void getUserMessageList(String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/user_msg_letters", params, handler); + } + + /** + * 发送消息 + * + * @param authorId 接收者 + * @param content 发送内容 + * @param handler 回调 + */ + public static void pubMessage(long authorId, String content, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("authorId", authorId); + params.put("content", content); + post("action/apiv2/messages_pub", params, handler); + } + + public static void pubMessage(long authorId, File content, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("authorId", authorId); + try { + params.put("file", content); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + post("action/apiv2/messages_pub", params, handler); + } + + /** + * 添加反馈,私信接口 + * + * @param authorId + * @param content + * @param file + * @param handler + */ + public static void pubMessage(long authorId, String content, File file, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("authorId", authorId); + params.put("content", content); + if (file != null && file.exists()) { + try { + params.put("file", file); + } catch (Exception e) { + e.printStackTrace(); + } + } + post("action/apiv2/messages_pub", params, handler); + } + + /** + * 获取AT我的列表。 + * + * @param pageToken pageToken + * @param handler 回调 + */ + public static void getMsgMentionList(String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/user_msg_mentions", params, handler); + } + + /** + * 评论我的列表 + * + * @param pageToken pageToken + * @param handler 回调 + */ + public static void getMsgCommentList(String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/user_msg_comments", params, handler); + } + + /** + * 获取某用户的信息 + * + * @param uid user id + * @param nick unique personal suffix + */ + public static void getUserInfo(long uid, String nick, String suffix, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + if (uid > 0) params.put("id", uid); + params.put("nickname", nick); + params.put("suffix", suffix); + ApiHttpClient.get("action/apiv2/user_info", params, handler); + } + + /** + * 获取某用户的动态(讨论)列表 + * + * @param uid user id + * @param pageToken page token + * @param handler async handler + */ + public static void getUserActives(long uid, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("id", uid); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/user_activity", params, handler); + } + + /** + * 获取当前的新消息数量 + * + * @param handler TextHttpResponseHandler + */ + public static void getNotice(TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("clear", false); + ApiHttpClient.get("action/apiv2/notice", params, handler); + } + + /** + * 清理消息 + * + * @param handler TextHttpResponseHandler + */ + public static void clearNotice(int flag, TextHttpResponseHandler handler) { + ApiHttpClient.post("action/apiv2/notice_clear", + new RequestParams("clearFlag", flag), + handler); + } + + + /** + * 获取个人信息 + */ + public static void getUserInfo(TextHttpResponseHandler handler) { + // RequestParams params = new RequestParams(); + // params.put("id", uid); + ApiHttpClient.get("action/apiv2/user_info", handler); + + } + + /** + * update the user icon + * + * @param file file + * @param handler handler + */ + public static void updateUserIcon(File file, TextHttpResponseHandler handler) { + if (file == null) return; + RequestParams params = new RequestParams(); + try { + params.put("portrait", file); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + post("action/apiv2/user_edit_portrait", params, handler); + + } + + /** + * @param type {@link #TYPE_USER_FLOWS ,#TYPE_USER_FANS} + * @param userId userId + * @param pageToken pageToken + * @param handler handler + */ + public static void getUserFansOrFlows(int type, long userId, String pageToken, TextHttpResponseHandler handler) { + if (userId <= 0) return; + RequestParams params = new RequestParams(); + params.put("id", userId); + params.put("pageToken", pageToken); + + String uri = "user_follows"; + if (type == TYPE_USER_FANS) { + uri = "user_fans"; + } + ApiHttpClient.get("action/apiv2/" + uri, params, handler); + } + + /** + * get user favorites + * + * @param catalog catalog {@link #CATALOG_ALL,#CATALOG_SOFTWARE,#CATALOG_QUESTION, + * {@link #CATALOG_BLOG,#CATALOG_TRANSLATION ,#CATALOG_EVENT,#CATALOG_NEWS} + * @param pagetoken pagetoken + * @param handler handler + */ + public static void getUserFavorites(int catalog, String pagetoken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("catalog", catalog); + params.put("pageToken", pagetoken); + ApiHttpClient.get("action/apiv2/favorites", params, handler); + } + + /** + * 摇一摇(抽奖) + * + * @param timestamp 当前摇一摇的时间戳 + * @param appToken App唯一校验 + * @param signature 加密后的字符串 + * @param handler + */ + public static void getShakePresent(long timestamp, String appToken, String signature, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("timestamp", timestamp); + params.put("appToken", appToken); + params.put("signature", signature); + post("action/apiv2/shake_present", params, handler); + } + + /** + * 摇一摇(综合) + * + * @param handler + */ + public static void getShakeNews(TextHttpResponseHandler handler) { + ApiHttpClient.get("action/apiv2/shake_news", handler); + } + + public static void reward(Map pairs, AsyncHttpResponseHandler handler) { + RequestParams params = new RequestParams(pairs); + post("action/apiv2/reward_order", params, handler); + } + + public static void checkUpdate(TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("appId", 1); + params.put("catalog", 1); + params.put("all", false); + ApiHttpClient.get("action/apiv2/product_version", params, handler); + } + + public static void getCollectionList(String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("catalog", 0); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/favorites", params, handler); + } + + /** + * @param catalog open catalog + * @param openInfo openInfo + * @param handler handler + */ + public static void openLogin(String catalog, String openInfo, TextHttpResponseHandler handler) { + if (TextUtils.isEmpty(openInfo)) return; + RequestParams params = new RequestParams(); + params.put("catalog", catalog); + params.put("info", openInfo); + + post("action/apiv2/account_open_login", params, handler); + } + + /** + * 搜索 + * + * @param catalog 搜索类型 + * @param content 搜索内容 + * @param pageToken next page token + * @param handler handler + */ + public static void search(int catalog, String content, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("catalog", catalog); + params.put("content", content); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/search", params, handler); + } + + /** + * login account + * + * @param username username + * @param pwd pwd + * @param handler handler + */ + public static void login(String username, String pwd, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("account", username); + params.put("password", pwd); + post("action/apiv2/account_login", params, handler); + } + + public static void sendSmsCode(String phone, int intent, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("phone", phone); + params.put("intent", intent); + post("action/apiv2/phone_send_code", params, handler); + } + + + /** + * validate and get phone token + * + * @param phoneNumber phoneNumber + * @param smsCode smsCode + * @param handler handler + */ + public static void validateRegisterInfo(String phoneNumber, String smsCode, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("phone", phoneNumber); + params.put("code", smsCode); + post("action/apiv2/phone_validate", params, handler); + } + + + /** + * register user info + * + * @param username username + * @param password pwd + * @param gender gender + * @param phoneToken token + * @param handler handler + */ + public static void register(String username, String password, int gender + , String phoneToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("username", username); + params.put("password", password); + params.put("gender", gender); + params.put("phoneToken", phoneToken); + + post("action/apiv2/account_register", params, handler); + } + + /** + * reset pwd + * + * @param password password + * @param phoneToken token + * @param handler handler + */ + public static void resetPwd(String password, String phoneToken + , TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("password", password); + params.put("phoneToken", phoneToken); + + post("action/apiv2/account_password_forgot", params, handler); + } + + /** + * 获得首页数据 + * + * @param api 动态api + * @param pageToken pageToken + * @param handler handler + */ + + public static void getSubscription(String api, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("pageToken", pageToken); + ApiHttpClient.get(api, params, handler); + } + + /** + * 获得banner + * + * @param api 动态api + * @param handler handler + */ + public static void getBanner(String api, TextHttpResponseHandler handler) { + ApiHttpClient.get(api, handler); + } + + /** + * 动弹列表 + * + * @param aid author id, 请求某人的动弹 + * @param tag 相关话题 + * @param type 1: 广场(所有动弹), 2:朋友圈(好友动弹) + * @param order 1: 最新, 2:最热 + * @param handler handler + */ + public static void getTweetList(Long aid, String tag, Integer type, Integer order + , String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + if (aid != null) { + params.put("authorId", aid); + } else if (!TextUtils.isEmpty(tag)) { + params.put("tag", tag); + } else { + params.put("type", type); + } + params.put("order", order); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/tweet_list", params, handler); + } + + /** + * 新版获得各种类型详情统一接口和Model + */ + public static void getDetail(int type, String ident, long id, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("type", type); + params.put("id", id); + if (!TextUtils.isEmpty(ident)) + params.put("ident", ident); + ApiHttpClient.get("action/apiv2/detail", params, handler); + } + + + /** + * 英文翻译 + * + * @param key key + * @param type type + * @param handler handler + */ + public static void translate(String key, int type, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("type", type); + params.put("key", key); + ApiHttpClient.get("action/apiv2/article_translate", params, handler); + } + + /** + * event signin + * + * @param sourceId sourceId + * @param phone phone + * @param handler handler + */ + public static void eventSignin(long sourceId, String phone, TextHttpResponseHandler handler) { + if (sourceId <= 0) return; + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + if (!TextUtils.isEmpty(phone)) + params.put("phone", phone); + ApiHttpClient.post("action/apiv2/event_signin", params, handler); + } + + /** + * 取消报名 + * + * @param id eventId + * @param handler handler + */ + public static void cancelApply(long id, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", id); + ApiHttpClient.post("action/apiv2/event_apply_cancel", params, handler); + } + + /** + * 新版获得活动报名参数 + */ + public static void getSignUpOptions(long sourceId, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + ApiHttpClient.get("action/apiv2/event_apply_preload", params, handler); + } + + /** + * 新版活动报名,动态参数 + */ + public static void signUpEvent(long sourceId, List options, TextHttpResponseHandler handler) { + StringParams params = new StringParams(); + params.putForm("sourceId", String.valueOf(sourceId)); + for (SignUpEventOptions option : options) { + params.putForm(option.getKey(), option.getValue()); + } + ApiHttpClient.post("action/apiv2/event_apply", params, handler); + } + + /** + * 获取用户自己的活动 + * + * @param authorId authorId + * @param authorName authorName + * @param pageToken pageToken + * @param handler handler + */ + public static void getUserEvent(long authorId, String authorName, String pageToken, TextHttpResponseHandler handler) { + if (authorId <= 0) return; + RequestParams params = new RequestParams(); + params.put("authorId", authorId); + params.put("authorName", authorName); + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/event_list", params, handler); + + } + + public static void syncSignUserInfo(long sourceId, TextHttpResponseHandler handler) { + if (sourceId <= 0) return; + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + ApiHttpClient.post("action/apiv2/event_apply_info", params, handler); + } + + /** + * 举报 + * + * @param sourceId 举报的内容数据Id + * @param type 举报资源的类型 + * @param href 举报的文章地址 + * @param reason 举报原因: + * @param memo 其他原因的描述字段 + * @param handler + */ + public static void report(long sourceId, + int type, + String href, + int reason, + String memo, + String key, + TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + params.put("type", type); + params.put("href", href); + params.put("reason", reason); + params.put("memo", memo); + if (!TextUtils.isEmpty(key)) { + params.put("key", key); + } + ApiHttpClient.post("action/apiv2/report", params, handler); + } + + /** + * 获取活动出席列表 + */ + public static void getApplyUsers(long sourceId, String pageToken, String filter, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("sourceId", sourceId); + if (!TextUtils.isEmpty(pageToken)) + params.put("pageToken", pageToken); + if (!TextUtils.isEmpty("filter")) { + params.put("filter", filter); + } + ApiHttpClient.post("action/apiv2/event_attendee_list", params, handler); + } + + /** + * 拉取用户好友(联系人) + * + * @param userId userId + * @param pageToken pageToken + * @param requestCount requestCount + * @param handler handler + */ + public static void getUserFriends(long userId, String pageToken, int requestCount, TextHttpResponseHandler handler) { + + if (userId <= 0) return; + RequestParams params = new RequestParams(); + params.put("id", userId); + params.put("pageToken", pageToken); + params.put("count", requestCount); + ApiHttpClient.get("action/apiv2/user_follows", params, handler); + + } + + /** + * 支付打赏接口信息拉取 + * + * @param authorId 被打赏作者 + * @param objId 文章id + * @param money 价格,支付宝单位元、微信单位分 + * @param payType 支付类型 1 支付宝 2、微信支付 返回结果不一样 + * @param handler handler + */ + public static void getPayDonate(long authorId, long objId, float money, int payType, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("authorId", authorId); + params.put("objId", objId); + params.put("money", Float.valueOf(money).intValue()); + params.put("payType", payType); + ApiHttpClient.get("action/apiv2/blog_donate_prepare", params, handler); + } + + /** + * 获取用户活动列表 + */ + public static void getUserEvents(long authorId, String authorName, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + if (authorId > 0) + params.put("authorId", authorId); + if (!TextUtils.isEmpty(authorName)) + params.put("authorName", authorName); + if (!TextUtils.isEmpty(pageToken)) + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/event_list", params, handler); + } + + /** + * 修改用户资料 + * 忽略则不修改 + * + * @param handler 回调 + */ + public static void updateUserInfo(String userName, + String signature, + String skill, + String field, + String province, + String city, + TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + if (!TextUtils.isEmpty(userName)) { + params.put("nickname", userName); + } + if (!TextUtils.isEmpty(signature)) { + params.put("signature", signature); + } + if (!TextUtils.isEmpty(skill)) { + params.put("skill", skill); + } + if (!TextUtils.isEmpty(field)) { + params.put("field", field); + } + if (!TextUtils.isEmpty(province)) { + params.put("province", province); + } + if (!TextUtils.isEmpty(city)) { + params.put("city", city); + } + ApiHttpClient.get("action/apiv2/user_edit_infos", params, handler); + } + + public static void csdnLogin(String nickname, String pwd, TextHttpResponseHandler handler) { + new AsyncHttpClient().get( + String.format("http://api.csdn.net/oauth2/access_token?client_id=1100506&client_secret=1583dcf438674318a84af97d40c44a93&grant_type=password&username=%s&password=%s", nickname, pwd), handler); + } + + /** + * 删除博客 + * + * @param id 博客ID + * @param handler 回调 + */ + public static void deleteBlog(long id, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("id", id); + ApiHttpClient.get("action/apiv2/delete_blog", params, handler); + } + + + /** + * 获取博客分类 + * + * @param handler 回调 + */ + public static void getBlogCategories(TextHttpResponseHandler handler) { + ApiHttpClient.get("action/apiv2/get_blog_category", handler); + } + + /** + * 发布博客 + * + * @param Blog 博客 + * @param handler 回调 + */ + public static void pubBlog(Blog blog, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("title", blog.getTitle()); + params.put("abstract", blog.getSummary()); + params.put("system", blog.getSystem()); + params.put("catalog", blog.getCatalog()); + params.put("canVisible", blog.getCanVisible()); + params.put("canComment", blog.getCanComment()); + params.put("isStick", blog.getIsStick()); + params.put("type", blog.getType()); + params.put("content", blog.getContent()); + ApiHttpClient.post("action/apiv2/pub_blog", params, handler); + } + + /** + * 获取头条 + * + * @param ident 手机唯一标示 + * @param pageToken pageToken + * @param handler handler + */ + public static void getArticles(String ident, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("ident", ident); + if (!TextUtils.isEmpty(pageToken)) { + params.put("pageToken", pageToken); + } + ApiHttpClient.get("action/apiv2/get_articles", params, handler); + } + + + /** + * 获取登陆用户标签 + * + * @param handler handler + */ + public static void getUserTags(TextHttpResponseHandler handler) { + ApiHttpClient.get("action/apiv2/user_tags", handler); + } + + /** + * 提交用户标签 + * + * @param handler handler + */ + public static void putUserTags(String ids, String deleteIds, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + if (!TextUtils.isEmpty(ids)) { + params.put("ids", ids); + } + if (!TextUtils.isEmpty(deleteIds)) { + params.put("deleteIds", deleteIds); + } + ApiHttpClient.post("action/apiv2/put_tags", params, handler); + } + + + /** + * 登陆用户搜索标签 + * + * @param keyword keyword + * @param handler handler + */ + public static void searchUserTags(String keyword, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("keyword", keyword); + if (!TextUtils.isEmpty(pageToken)) { + params.put("pageToken", pageToken); + Log.e("pageToken", pageToken); + } + ApiHttpClient.post("action/apiv2/search_tags_by_name", params, handler); + } + + /** + * 获取头条 + * + * @param ident 手机唯一标示 + * @param pageToken pageToken + * @param handler handler + */ + public static void getArticles(String ident, int type, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("ident", ident); + params.put("type", type); + if (!TextUtils.isEmpty(pageToken)) { + params.put("pageToken", pageToken); + } + ApiHttpClient.get("action/apiv2/get_articles", params, handler); + } + + /** + * 获取头条 + * + * @param key key + * @param ident 手机唯一标示 + * @param handler handler + */ + public static void getArticleDetail(String key, String ident, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("ident", ident); + params.put("key", key); + ApiHttpClient.get("action/apiv2/get_article_detail", params, handler); + } + + /** + * 获取头条 + * + * @param key key + * @param ident 手机唯一标示 + * @param handler handler + */ + public static void getArticleDetail(String key, String ident, int type, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("ident", ident); + params.put("key", key); + params.put("type", type); + ApiHttpClient.get("action/apiv2/get_article_detail", params, handler); + } + + /** + * 获取头条相关推荐 + * recommend_article_es get_article_recommends + * + * @param ident 手机唯一标示 + * @param pageToken pageToken + * @param handler handler + */ + public static void getArticleRecommends(String key, String ident, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("key", key); + params.put("ident", ident); + if (!TextUtils.isEmpty(pageToken)) { + params.put("pageToken", pageToken); + } + ApiHttpClient.get("action/apiv2/get_article_recommends", params, handler); + } + + /** + * 获取头条相关推荐 + * recommend_article_es get_article_recommends + * + * @param ident 手机唯一标示 + * @param pageToken pageToken + * @param handler handler + */ + public static void getArticleRecommends(String key, String ident, int type, + String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("key", key); + params.put("ident", ident); + params.put("type", type); + if (!TextUtils.isEmpty(pageToken)) { + params.put("pageToken", pageToken); + } + ApiHttpClient.get("action/apiv2/get_article_recommends", params, handler); + } + + /** + * 获取头条评论 + * + * @param key key + * @param catalog 1:获取热门评论 2:最新所有评论 + * @param pageToken catalog=1时不需要,catalog=2时按分页获取规则 + * @param handler 回调 + */ + public static void getArticleComments(String key, int catalog, String pageToken, TextHttpResponseHandler handler) { + if (TextUtils.isEmpty(key)) return; + RequestParams params = new RequestParams(); + params.put("key", key); + params.put("catalog", catalog); + if (!TextUtils.isEmpty(pageToken)) + params.put("pageToken", pageToken); + ApiHttpClient.get("action/apiv2/get_article_comments", params, handler); + } + + + /** + * 头条评论点赞 + * + * @param commentId commentId + * @param commentAuthorId commentAuthorId + * @param handler handler + */ + public static void voteArticleComment(long commentId, long commentAuthorId, TextHttpResponseHandler handler) { + if (commentId <= 0) return; + RequestParams params = new RequestParams(); + params.put("commentId", commentId); + params.put("commentAuthorId", commentAuthorId); + post("action/apiv2/comment_vote", params, handler); + + } + + /** + * 发布评论 + */ + public static void pubArticleComment(String key, + String content, + long referId, + long reAuthorId, + TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("key", key); + + params.put("content", content); + if (referId > 0) + params.put("referId", referId); + if (reAuthorId > 0) + params.put("reAuthorId", reAuthorId); + post("action/apiv2/pub_article_comment", params, handler); + } + + + /** + * 发布评论 + */ + public static void pubArticleComment(String key, + String content, + int type, + long referId, + long reAuthorId, + TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("key", key); + params.put("type", type); + params.put("content", content); + if (referId > 0) + params.put("referId", referId); + if (reAuthorId > 0) + params.put("reAuthorId", reAuthorId); + post("action/apiv2/pub_article_comment", params, handler); + } + + + /** + * 收集阅读习惯 + */ + public static void pushReadRecord(String key, + TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("key", key); + post("action/apiv2/send_read_record", params, handler); + } + + + /** + * 获取启动页展示 + * + * @param handler handler + */ + public static void getLauncher(TextHttpResponseHandler handler) { + ApiHttpClient.get("action/apiv2/get_launcher", handler); + } + + /** + * 统计点击数 + * + * @param key key + * @param handler handler + */ + public static void addClickCount(String key, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("key", key); + ApiHttpClient.get("action/apiv2/click_count", params, handler); + } + + /** + * 收藏推荐 + * + * @param json json + * @param handler handler + */ + public static void articleFav(String json, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("article", json); + ApiHttpClient.post("action/apiv2/favorite_hot_article", params, handler); + } + + + /** + * 投递文章 + * + * @param link link + * @param handler handler + */ + public static void putArticle(String link, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("link", link); + ApiHttpClient.post("action/apiv2/post_link_article", params, handler); + } + + + /** + * 获取微信公众号解析规则 + * @param handler handler + */ + public static void getWXRule(TextHttpResponseHandler handler) { + ApiHttpClient.get("action/apiv2/wechat_title_rule", handler); + } + + /** + * 阅读记录 + * + * @param handler handler + */ + public static void readHistory(String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + if (!TextUtils.isEmpty(pageToken)) { + params.put("pageToken", pageToken); + } + ApiHttpClient.get("action/apiv2/user_read_list", handler); + } + + /** + * 搜索界面 + * + * @param type -1 | 不论搜索类型,默认为-1,全局搜索,authors控制数量,softwares也控制数量 + * 0 | 搜索article,只返回articles节点 + * 1 | 搜索软件,只返回softwares节点 + * 11 | 搜索用户,只返回authors节点 + * @param order 0 | 按默认排序方式相关度 + * 1 | 相关度 + * 2 | 按热度排序(综合评论、阅读数) + * 3 | 按最新时间排序 + * @param keyword 关键字 + * @param pageToken token + * @param handler handler + */ + public static void search(int type, int order, String keyword, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + if (!TextUtils.isEmpty(pageToken)) { + params.put("pageToken", pageToken); + } + params.put("type", type); + params.put("order", order); + params.put("keyword", keyword); + ApiHttpClient.post("action/apiv2/search_articles", params, handler); + } + + /** + * 搜索软件 + * + * @param type type + * @param keyword keyword + * @param pageToken pageToken + * @param handler handler + */ + public static void searchSoftware(int type, String keyword, String pageToken, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + if (!TextUtils.isEmpty(pageToken)) { + params.put("pageToken", pageToken); + } + params.put("type", type); + params.put("keyword", keyword); + ApiHttpClient.post("action/apiv2/search_articles", params, handler); + } + + /** + * 去广告规则 + * + * @param url url + * @param handler handler + */ + public static void getWebRule(String url, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + if (!TextUtils.isEmpty(url)) { + params.put("url", url); + } + ApiHttpClient.get("action/apiv2/get_article_rules", params, handler); } } diff --git a/app/src/main/java/net/oschina/app/api/remote/OSChinaTeamApi.java b/app/src/main/java/net/oschina/app/api/remote/OSChinaTeamApi.java index 584b19d7746fa260450acdbe96a3300a5b25ac61..eeb9bc533485dfe5665e2bccf3fe652c52631c1b 100644 --- a/app/src/main/java/net/oschina/app/api/remote/OSChinaTeamApi.java +++ b/app/src/main/java/net/oschina/app/api/remote/OSChinaTeamApi.java @@ -1,34 +1,36 @@ package net.oschina.app.api.remote; -import java.io.File; -import java.io.FileNotFoundException; +import android.content.Context; +import android.text.TextUtils; + +import com.loopj.android.http.AsyncHttpResponseHandler; +import com.loopj.android.http.RequestParams; import net.oschina.app.AppContext; import net.oschina.app.api.ApiHttpClient; +import net.oschina.app.improve.account.AccountHelper; import net.oschina.app.team.bean.TeamIssue; import net.oschina.app.team.bean.TeamProject; -import android.text.TextUtils; -import com.loopj.android.http.AsyncHttpResponseHandler; -import com.loopj.android.http.RequestParams; +import java.io.File; +import java.io.FileNotFoundException; /** * osc team api集合类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @version 创建时间:2015年1月14日 下午3:32:18 - * */ public class OSChinaTeamApi { /** * 获取团队项目列表 - * + * * @param teamId * @param handler */ public static void getTeamProjectList(int teamId, - AsyncHttpResponseHandler handler) { + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamId); ApiHttpClient.get("action/api/team_project_list", params, handler); @@ -36,14 +38,14 @@ public class OSChinaTeamApi { /** * 获取team动态列表 - * + * * @param teamId * @param activeId * @param pageIndex * @param handler */ public static void getTeamCommentList(int teamId, int activeId, - int pageIndex, AsyncHttpResponseHandler handler) { + int pageIndex, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamId); params.put("id", activeId); @@ -55,19 +57,18 @@ public class OSChinaTeamApi { /*** * 获取团队绑定项目的成员列表(包括管理员以及开发者) - * - * @author 火蚁 2015-2-5 下午6:45:41 - * - * @return void + * * @param teamId * @param teamProject * @param handler + * @return void + * @author 火蚁 2015-2-5 下午6:45:41 */ public static void getTeamProjectMemberList(int teamId, - TeamProject teamProject, AsyncHttpResponseHandler handler) { + TeamProject teamProject, AsyncHttpResponseHandler handler, Context context) { RequestParams params = new RequestParams(); params.put("teamid", teamId); - params.put("uid", AppContext.getInstance().getLoginUid()); + params.put("uid", AccountHelper.getUserId()); params.put("projectid", teamProject.getGit().getId()); String source = teamProject.getSource(); if (source != null && !TextUtils.isEmpty(source)) { @@ -80,20 +81,18 @@ public class OSChinaTeamApi { /*** * 获取项目的动态列表 - * - * @author 火蚁 2015-3-2 下午5:18:54 - * - * @return void + * * @param teamId * @param project - * @param type - * "all"(default),"issue","code","other" + * @param type "all"(default),"issue","code","other" * @param page * @param handler + * @return void + * @author 火蚁 2015-3-2 下午5:18:54 */ public static void getTeamProjectActiveList(int teamId, - TeamProject project, String type, int page, - AsyncHttpResponseHandler handler) { + TeamProject project, String type, int page, + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamId); params.put("projectid", project.getGit().getId()); @@ -108,18 +107,14 @@ public class OSChinaTeamApi { /** * 获取某项目的任务列表 - * - * @param uId - * 用户id - * @param teamId - * 团队id - * @param projectId - * 项目id(当<=0或不设置时,查询非项目的任务列表) - * @param source - * "Git@OSC","GitHub"(只有设置了projectid值,这里才需要设置该值) + * + * @param uId 用户id + * @param teamId 团队id + * @param projectId 项目id(当<=0或不设置时,查询非项目的任务列表) + * @param source "Git@OSC","GitHub"(只有设置了projectid值,这里才需要设置该值) */ public static void getTeamCatalogIssueList(int uId, int teamId, - int projectId, String source, AsyncHttpResponseHandler handler) { + int projectId, String source, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("uid", uId); params.put("teamid", teamId); @@ -131,28 +126,22 @@ public class OSChinaTeamApi { /** * 获取指定任务列表的任务列表 - * + * * @param teamId - * @param projectId - * 项目id(-1获取非项目任务列表, 0获取所有任务列表) - * @param catalogId - * 任务列表的的目录id - * @param source - * "Team@OSC"(default),"Git@OSC","GitHub",如果指定了projectid的值, - * 这个值就是必须的 - * @param uid - * 如果指定该值,则获取该id用户相关的任务 - * @param state - * "all"(default),"opened","closed","outdate" - * @param scope - * "tome"(default,指派给我的任务),"meto"(我指派的任务) + * @param projectId 项目id(-1获取非项目任务列表, 0获取所有任务列表) + * @param catalogId 任务列表的的目录id + * @param source "Team@OSC"(default),"Git@OSC","GitHub",如果指定了projectid的值, + * 这个值就是必须的 + * @param uid 如果指定该值,则获取该id用户相关的任务 + * @param state "all"(default),"opened","closed","outdate" + * @param scope "tome"(default,指派给我的任务),"meto"(我指派的任务) * @param pageIndex * @param pageSize * @param handler */ public static void getTeamIssueList(int teamId, int projectId, - int catalogId, String source, int uid, String state, String scope, - int pageIndex, int pageSize, AsyncHttpResponseHandler handler) { + int catalogId, String source, int uid, String state, String scope, + int pageIndex, int pageSize, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamId); params.put("projectid", projectId); @@ -168,22 +157,20 @@ public class OSChinaTeamApi { /*** * 改变一个任务的状态 - * - * @author 火蚁 2015-3-6 上午11:44:01 - * - * @return void + * * @param teamId * @param issue - * @param target - * 修改的属性("state" : 状态, "assignee" 指派人, "deadline" : 截止日期) + * @param target 修改的属性("state" : 状态, "assignee" 指派人, "deadline" : 截止日期) * @param handler + * @return void + * @author 火蚁 2015-3-6 上午11:44:01 */ public static void changeIssueState(int teamId, TeamIssue issue, - String target, AsyncHttpResponseHandler handler) { + String target, AsyncHttpResponseHandler handler, Context context) { if (issue == null) return; RequestParams params = new RequestParams(); - params.put("uid", AppContext.getInstance().getLoginUid()); + params.put("uid", AccountHelper.getUserId()); params.put("teamid", teamId); params.put("target", target); params.put("issueid", issue.getId()); @@ -198,13 +185,13 @@ public class OSChinaTeamApi { } public static void pubTeamNewIssue(RequestParams params, - AsyncHttpResponseHandler handler) { + AsyncHttpResponseHandler handler) { ApiHttpClient.post("action/api/team_issue_pub", params, handler); } /*** * 获取团队的讨论区列表 - * + * * @param type * @param teamId * @param uid @@ -212,7 +199,7 @@ public class OSChinaTeamApi { * @param handler */ public static void getTeamDiscussList(String type, int teamId, int uid, - int pageIndex, AsyncHttpResponseHandler handler) { + int pageIndex, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("type", type); params.put("teamid", teamId); @@ -224,16 +211,15 @@ public class OSChinaTeamApi { /*** * 获取讨论贴详情 - * - * @author 火蚁 2015-2-2 下午6:19:54 - * - * @return void + * * @param teamId * @param discussId * @param handler + * @return void + * @author 火蚁 2015-2-2 下午6:19:54 */ public static void getTeamDiscussDetail(int teamId, int discussId, - AsyncHttpResponseHandler handler) { + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamId); params.put("discussid", discussId); @@ -242,18 +228,17 @@ public class OSChinaTeamApi { /*** * 发表讨论贴评论 - * - * @author 火蚁 2015-2-3 下午2:42:54 - * - * @return void + * * @param uid * @param teamId - * @param dicussId + * @param discussId * @param content * @param handler + * @return void + * @author 火蚁 2015-2-3 下午2:42:54 */ public static void pubTeamDiscussReply(int uid, int teamId, int discussId, - String content, AsyncHttpResponseHandler handler) { + String content, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("uid", uid); params.put("teamid", teamId); @@ -264,21 +249,19 @@ public class OSChinaTeamApi { /*** * 发表一条综合评论 动态、分享内容、周报 - * - * @author 火蚁 2015-3-6 下午3:31:07 - * - * @return void + * * @param teamId - * @param type - * 普通动态-110,分享内容-114, 周报-118 + * @param type 普通动态-110,分享内容-114, 周报-118 * @param tweetId * @param content * @param handler + * @return void + * @author 火蚁 2015-3-6 下午3:31:07 */ public static void pubTeamTweetReply(int teamId, int type, long tweetId, - String content, AsyncHttpResponseHandler handler) { + String content, AsyncHttpResponseHandler handler, Context context) { RequestParams params = new RequestParams(); - params.put("uid", AppContext.getInstance().getLoginUid()); + params.put("uid", AccountHelper.getUserId()); params.put("type", type); params.put("teamid", teamId); params.put("tweetid", tweetId); @@ -288,12 +271,11 @@ public class OSChinaTeamApi { /*** * 获取团队任务详情 - * + * * @author 火蚁 2015-1-27 下午7:47:17 - * */ public static void getTeamIssueDetail(int teamId, int issueId, - AsyncHttpResponseHandler handler) { + AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamId); params.put("issueid", issueId); @@ -302,7 +284,7 @@ public class OSChinaTeamApi { /*** * 获取团队的周报列表 - * + * * @param uid * @param teamId * @param year @@ -311,7 +293,7 @@ public class OSChinaTeamApi { * @param handler */ public static void getTeamDiaryList(int uid, int teamId, int year, - int week, int pageIndex, AsyncHttpResponseHandler handler) { + int week, int pageIndex, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("uid", uid); params.put("teamid", teamId); @@ -324,19 +306,17 @@ public class OSChinaTeamApi { /*** * 任务、周报、讨论的回复列表 - * - * @author 火蚁 2015-2-2 上午11:39:04 - * - * @return void + * * @param teamId * @param id - * @param type - * 评论列表的类型(周报diary,讨论discuss,任务issue) + * @param type 评论列表的类型(周报diary,讨论discuss,任务issue) * @param pageIndex * @param handler + * @return void + * @author 火蚁 2015-2-2 上午11:39:04 */ public static void getTeamReplyList(int teamId, int id, String type, - int pageIndex, AsyncHttpResponseHandler handler) { + int pageIndex, AsyncHttpResponseHandler handler) { RequestParams params = new RequestParams(); params.put("teamid", teamId); params.put("id", id); @@ -349,20 +329,19 @@ public class OSChinaTeamApi { /*** * 发表一个新的团队动态 - * - * @author 火蚁 2015-3-9 下午2:46:13 - * - * @return void + * * @param teamId * @param content * @param img * @param handler + * @return void + * @author 火蚁 2015-3-9 下午2:46:13 */ public static void pubTeamNewActive(int teamId, String content, File img, - AsyncHttpResponseHandler handler) { + AsyncHttpResponseHandler handler, Context context) { RequestParams params = new RequestParams(); params.put("teamid", teamId); - params.put("uid", AppContext.getInstance().getLoginUid()); + params.put("uid", AccountHelper.getUserId()); params.put("msg", content); params.put("appid", 3); if (img != null) { @@ -378,19 +357,18 @@ public class OSChinaTeamApi { /*** * 更新子任务属性 - * - * @author 火蚁 2015-3-10 下午4:53:49 - * - * @return void + * * @param teamId * @param target * @param childIssue * @param handler + * @return void + * @author 火蚁 2015-3-10 下午4:53:49 */ public static void updateChildIssue(int teamId, String target, - TeamIssue childIssue, AsyncHttpResponseHandler handler) { + TeamIssue childIssue, AsyncHttpResponseHandler handler, Context context) { RequestParams params = new RequestParams(); - params.put("uid", AppContext.getInstance().getLoginUid()); + params.put("uid", AccountHelper.getUserId()); params.put("teamid", teamId); params.put("childissueid", childIssue.getId()); params.put("target", target); @@ -405,19 +383,18 @@ public class OSChinaTeamApi { /*** * 发表任务评论 - * - * @author 火蚁 2015-3-13 下午6:22:41 - * - * @return void + * * @param teamId * @param issueId * @param content * @param handler + * @return void + * @author 火蚁 2015-3-13 下午6:22:41 */ public static void pubTeamIssueReply(int teamId, int issueId, - String content, AsyncHttpResponseHandler handler) { + String content, AsyncHttpResponseHandler handler, Context context) { RequestParams params = new RequestParams(); - params.put("uid", AppContext.getInstance().getLoginUid()); + params.put("uid", AccountHelper.getUserId()); params.put("teamid", teamId); params.put("content", content); params.put("issueid", issueId); diff --git a/app/src/main/java/net/oschina/app/base/BaseActivity.java b/app/src/main/java/net/oschina/app/base/BaseActivity.java index 99c32cd5802a89115de2d82d4d62bc1b035725e5..5f56190eef765c6ea61e5c7ca702433d5ecff22c 100644 --- a/app/src/main/java/net/oschina/app/base/BaseActivity.java +++ b/app/src/main/java/net/oschina/app/base/BaseActivity.java @@ -3,85 +3,89 @@ package net.oschina.app.base; import android.app.ProgressDialog; import android.os.Bundle; import android.support.v7.app.ActionBar; -import android.support.v7.app.ActionBarActivity; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; import android.view.LayoutInflater; -import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.TextView; -import net.oschina.app.AppContext; -import net.oschina.app.AppManager; import net.oschina.app.R; +import net.oschina.app.improve.utils.DialogHelper; import net.oschina.app.interf.BaseViewInterface; import net.oschina.app.ui.dialog.CommonToast; import net.oschina.app.ui.dialog.DialogControl; -import net.oschina.app.util.DialogHelp; import net.oschina.app.util.TDevice; -import org.kymjs.kjframe.utils.StringUtils; - import butterknife.ButterKnife; /** * baseActionBar Activity - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年9月25日 上午11:30:15 引用自:tonlin */ -public abstract class BaseActivity extends ActionBarActivity implements +public abstract class BaseActivity extends AppCompatActivity implements DialogControl, View.OnClickListener, BaseViewInterface { - public static final String INTENT_ACTION_EXIT_APP = "INTENT_ACTION_EXIT_APP"; - private boolean _isVisible; private ProgressDialog _waitDialog; protected LayoutInflater mInflater; protected ActionBar mActionBar; - private TextView mTvActionTitle; - @Override - protected void onDestroy() { - super.onDestroy(); - TDevice.hideSoftKeyboard(getCurrentFocus()); - ButterKnife.reset(this); - } + private final String packageName4Umeng = this.getClass().getName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (AppContext.getNightModeSwitch()) { - setTheme(R.style.AppBaseTheme_Night); - } else { - setTheme(R.style.AppBaseTheme_Light); - } - AppManager.getAppManager().addActivity(this); - if (!hasActionBar()) { - // supportRequestWindowFeature(Window.FEATURE_NO_TITLE); - } + setTheme(R.style.App_Theme_Light); onBeforeSetContentLayout(); if (getLayoutId() != 0) { setContentView(getLayoutId()); } mActionBar = getSupportActionBar(); mInflater = getLayoutInflater(); + if (hasActionBar()) { initActionBar(mActionBar); } // 通过注解绑定控件 - ButterKnife.inject(this); + ButterKnife.bind(this); init(savedInstanceState); initView(); initData(); _isVisible = true; + + //umeng analytics +// MobclickAgent.setDebugMode(false); +// MobclickAgent.openActivityDurationTrack(false); +// MobclickAgent.setScenarioType(this, MobclickAgent.EScenarioType.E_UM_NORMAL); + } + + @Override + protected void onPause() { + super.onPause(); +// MobclickAgent.onPageEnd(this.packageName4Umeng); +// MobclickAgent.onResume(this); + + if (this.isFinishing()) { + TDevice.hideSoftKeyboard(getCurrentFocus()); + } + } + + @Override + protected void onResume() { + super.onResume(); +// MobclickAgent.onPageStart(this.packageName4Umeng); +// MobclickAgent.onResume(this); } - protected void onBeforeSetContentLayout() {} + protected void onBeforeSetContentLayout() { + } protected boolean hasActionBar() { - return true; + return getSupportActionBar() != null; } protected int getLayoutId() { @@ -100,7 +104,8 @@ public abstract class BaseActivity extends ActionBarActivity implements return false; } - protected void init(Bundle savedInstanceState) {} + protected void init(Bundle savedInstanceState) { + } protected void initActionBar(ActionBar actionBar) { if (actionBar == null) @@ -125,13 +130,10 @@ public abstract class BaseActivity extends ActionBarActivity implements } public void setActionBarTitle(String title) { - if (StringUtils.isEmpty(title)) { + if (TextUtils.isEmpty(title)) { title = getString(R.string.app_name); } if (hasActionBar() && mActionBar != null) { - if (mTvActionTitle != null) { - mTvActionTitle.setText(title); - } mActionBar.setTitle(title); } } @@ -139,26 +141,16 @@ public abstract class BaseActivity extends ActionBarActivity implements @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - break; + case android.R.id.home: + onBackPressed(); + break; - default: - break; + default: + break; } return super.onOptionsItemSelected(item); } - @Override - protected void onPause() { - super.onPause(); - } - - @Override - protected void onResume() { - super.onResume(); - } - public void showToast(int msgResid, int icon, int gravity) { showToast(getString(msgResid), icon, gravity); } @@ -185,7 +177,7 @@ public abstract class BaseActivity extends ActionBarActivity implements public ProgressDialog showWaitDialog(String message) { if (_isVisible) { if (_waitDialog == null) { - _waitDialog = DialogHelp.getWaitDialog(this, message); + _waitDialog = DialogHelper.getProgressDialog(this, message); } if (_waitDialog != null) { _waitDialog.setMessage(message); @@ -207,11 +199,4 @@ public abstract class BaseActivity extends ActionBarActivity implements } } } - - @Override - public boolean onMenuOpened(int featureId, Menu menu) { - - // setOverflowIconVisible(featureId, menu); - return super.onMenuOpened(featureId, menu); - } } diff --git a/app/src/main/java/net/oschina/app/base/BaseApplication.java b/app/src/main/java/net/oschina/app/base/BaseApplication.java index 975c33f795db1ec96c248a3af38455603d3059ef..658aab16b1a45c73b8a17ca3f3783f3b9a5776a8 100644 --- a/app/src/main/java/net/oschina/app/base/BaseApplication.java +++ b/app/src/main/java/net/oschina/app/base/BaseApplication.java @@ -1,135 +1,49 @@ package net.oschina.app.base; import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.app.Activity; import android.app.Application; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; -import android.content.res.Resources; -import android.os.Build; -import android.util.DisplayMetrics; +import android.support.v4.content.SharedPreferencesCompat; import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; import android.widget.Toast; -import net.oschina.app.R; -import net.oschina.app.util.StringUtils; +import net.oschina.app.improve.widget.SimplexToast; @SuppressLint("InflateParams") public class BaseApplication extends Application { - private static String PREF_NAME = "creativelocker.pref"; - private static String LAST_REFRESH_TIME = "last_refresh_time.pref"; + private static final String PREF_NAME = "creativelocker.pref"; static Context _context; - static Resources _resource; - private static String lastToast = ""; - private static long lastToastTime; - - private static boolean sIsAtLeastGB; - - static { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { - sIsAtLeastGB = true; - } - } @Override public void onCreate() { super.onCreate(); _context = getApplicationContext(); - _resource = _context.getResources(); + //LeakCanary.install(this); } public static synchronized BaseApplication context() { return (BaseApplication) _context; } - public static Resources resources() { - return _resource; - } - - /** - * 放入已读文章列表中 - * - */ - public static void putReadedPostList(String prefFileName, String key, - String value) { - SharedPreferences preferences = getPreferences(prefFileName); - int size = preferences.getAll().size(); - Editor editor = preferences.edit(); - if (size >= 100) { - editor.clear(); - } - editor.putString(key, value); - apply(editor); - } - - /** - * 读取是否是已读的文章列表 - * - * @return - */ - public static boolean isOnReadedPostList(String prefFileName, String key) { - return getPreferences(prefFileName).contains(key); - } - - /** - * 记录列表上次刷新时间 - * - * @param key - * @param value - * @return void - * @author 火蚁 - * 2015-2-9 下午2:21:37 - */ - public static void putToLastRefreshTime(String key, String value) { - SharedPreferences preferences = getPreferences(LAST_REFRESH_TIME); - Editor editor = preferences.edit(); - editor.putString(key, value); - apply(editor); - } - - /** - * 获取列表的上次刷新时间 - * - * @param key - * @return - * @author 火蚁 - * 2015-2-9 下午2:22:04 - */ - public static String getLastRefreshTime(String key) { - return getPreferences(LAST_REFRESH_TIME).getString(key, StringUtils.getCurTimeStr()); - } - - @TargetApi(Build.VERSION_CODES.GINGERBREAD) - public static void apply(SharedPreferences.Editor editor) { - if (sIsAtLeastGB) { - editor.apply(); - } else { - editor.commit(); - } - } public static void set(String key, int value) { Editor editor = getPreferences().edit(); editor.putInt(key, value); - apply(editor); + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); } public static void set(String key, boolean value) { Editor editor = getPreferences().edit(); editor.putBoolean(key, value); - apply(editor); + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); } public static void set(String key, String value) { Editor editor = getPreferences().edit(); editor.putString(key, value); - apply(editor); + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); } public static boolean get(String key, boolean defValue) { @@ -152,41 +66,8 @@ public class BaseApplication extends Application { return getPreferences().getFloat(key, defValue); } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) public static SharedPreferences getPreferences() { - SharedPreferences pre = context().getSharedPreferences(PREF_NAME, - Context.MODE_MULTI_PROCESS); - return pre; - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public static SharedPreferences getPreferences(String prefName) { - return context().getSharedPreferences(prefName, - Context.MODE_MULTI_PROCESS); - } - - public static int[] getDisplaySize() { - return new int[]{getPreferences().getInt("screen_width", 480), - getPreferences().getInt("screen_height", 854)}; - } - - public static void saveDisplaySize(Activity activity) { - DisplayMetrics displaymetrics = new DisplayMetrics(); - activity.getWindowManager().getDefaultDisplay() - .getMetrics(displaymetrics); - SharedPreferences.Editor editor = getPreferences().edit(); - editor.putInt("screen_width", displaymetrics.widthPixels); - editor.putInt("screen_height", displaymetrics.heightPixels); - editor.putFloat("density", displaymetrics.density); - editor.commit(); - } - - public static String string(int id) { - return _resource.getString(id); - } - - public static String string(int id, Object... args) { - return _resource.getString(id, args); + return context().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); } public static void showToast(int message) { @@ -231,34 +112,9 @@ public class BaseApplication extends Application { showToast(context().getString(message, args), duration, icon, gravity); } - public static void showToast(String message, int duration, int icon, - int gravity) { - if (message != null && !message.equalsIgnoreCase("")) { - long time = System.currentTimeMillis(); - if (!message.equalsIgnoreCase(lastToast) - || Math.abs(time - lastToastTime) > 2000) { - View view = LayoutInflater.from(context()).inflate( - R.layout.view_toast, null); - ((TextView) view.findViewById(R.id.title_tv)).setText(message); - if (icon != 0) { - ((ImageView) view.findViewById(R.id.icon_iv)) - .setImageResource(icon); - ((ImageView) view.findViewById(R.id.icon_iv)) - .setVisibility(View.VISIBLE); - } - Toast toast = new Toast(context()); - toast.setView(view); - if (gravity == Gravity.CENTER) { - toast.setGravity(gravity, 0, 0); - } else { - toast.setGravity(gravity, 0, 35); - } - - toast.setDuration(duration); - toast.show(); - lastToast = message; - lastToastTime = System.currentTimeMillis(); - } - } + public static void showToast(String message, int duration, int icon, int gravity) { + Context context = _context; + if (context != null) + SimplexToast.show(context, message, gravity, duration); } } diff --git a/app/src/main/java/net/oschina/app/base/BaseFragment.java b/app/src/main/java/net/oschina/app/base/BaseFragment.java index 73caa6d8bc12ea6d157c2dbc21a146586b9b77b2..a7bb30a80319d34bc6b8ec6c287855c2954b4a56 100644 --- a/app/src/main/java/net/oschina/app/base/BaseFragment.java +++ b/app/src/main/java/net/oschina/app/base/BaseFragment.java @@ -15,10 +15,9 @@ import net.oschina.app.ui.dialog.DialogControl; /** * 碎片基类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年9月25日 上午11:18:46 - * */ public class BaseFragment extends Fragment implements android.view.View.OnClickListener, BaseFragmentInterface { @@ -42,7 +41,7 @@ public class BaseFragment extends Fragment implements @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + Bundle savedInstanceState) { this.mInflater = inflater; View view = super.onCreateView(inflater, container, savedInstanceState); return view; diff --git a/app/src/main/java/net/oschina/app/base/BaseListFragment.java b/app/src/main/java/net/oschina/app/base/BaseListFragment.java index f7790bbee384f7942e6babe27628bf2a68d2fee2..d50e4cc32045684dbfa58d3c7cb4ace19ccdd30f 100644 --- a/app/src/main/java/net/oschina/app/base/BaseListFragment.java +++ b/app/src/main/java/net/oschina/app/base/BaseListFragment.java @@ -30,7 +30,6 @@ import net.oschina.app.util.TDevice; import net.oschina.app.util.ThemeSwitchUtils; import net.oschina.app.util.XmlUtils; -import cz.msebera.android.httpclient.Header; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.Serializable; @@ -38,25 +37,25 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; +import cz.msebera.android.httpclient.Header; @SuppressLint("NewApi") public abstract class BaseListFragment extends BaseFragment - implements SwipeRefreshLayout.OnRefreshListener, OnItemClickListener, - OnScrollListener { + implements SwipeRefreshLayout.OnRefreshListener, OnItemClickListener, OnScrollListener { public static final String BUNDLE_KEY_CATALOG = "BUNDLE_KEY_CATALOG"; - @InjectView(R.id.swiperefreshlayout) + @Bind(R.id.swiperefreshlayout) protected SwipeRefreshLayout mSwipeRefreshLayout; - @InjectView(R.id.listview) + @Bind(R.id.listview) protected ListView mListView; protected ListBaseAdapter mAdapter; - @InjectView(R.id.error_layout) + @Bind(R.id.error_layout) protected EmptyLayout mErrorLayout; protected int mStoreEmptyState = -1; @@ -69,6 +68,36 @@ public abstract class BaseListFragment extends BaseFragment private AsyncTask> mCacheTask; private ParserTask mParserTask; + protected AsyncHttpResponseHandler mHandler = new AsyncHttpResponseHandler() { + + @Override + public void onSuccess(int statusCode, Header[] headers, + byte[] responseBytes) { + if (mCurrentPage == 0 && needAutoRefresh()) { + //AppContext.putToLastRefreshTime(getCacheKey(), + // StringUtils.getCurrentTimeStr()); + } + if (isAdded()) { + if (mState == STATE_REFRESH) { + onRefreshNetworkSuccess(); + } + executeParserTask(responseBytes); + } else { + executeOnLoadFinish(); + } + } + + @Override + public void onFailure(int arg0, Header[] arg1, byte[] arg2, + Throwable arg3) { + if (isAdded()) { + readCacheData(getCacheKey()); + } else { + executeOnLoadFinish(); + } + } + + }; @Override protected int getLayoutId() { @@ -77,7 +106,8 @@ public abstract class BaseListFragment extends BaseFragment @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + Bundle savedInstanceState) { + mInflater = inflater; View view = inflater.inflate(getLayoutId(), container, false); return view; } @@ -85,7 +115,7 @@ public abstract class BaseListFragment extends BaseFragment @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); initView(view); } @@ -187,11 +217,11 @@ public abstract class BaseListFragment extends BaseFragment @Override public void onItemClick(AdapterView parent, View view, int position, - long id) {} + long id) { + } private String getCacheKey() { - return new StringBuilder(getCacheKeyPrefix()).append("_") - .append(mCurrentPage).toString(); + return getCacheKeyPrefix() + "_" + mCurrentPage; } // 是否需要自动刷新 @@ -202,11 +232,9 @@ public abstract class BaseListFragment extends BaseFragment /*** * 获取列表数据 * - * - * @author 火蚁 2015-2-9 下午3:16:12 - * - * @return void * @param refresh + * @return void + * @author 火蚁 2015-2-9 下午3:16:12 */ protected void requestData(boolean refresh) { String key = getCacheKey(); @@ -214,18 +242,17 @@ public abstract class BaseListFragment extends BaseFragment readCacheData(key); } else { // 取新的数据 - sendRequestData(); + } + sendRequestData(); } /*** * 判断是否需要读取缓存的数据 * - * @author 火蚁 2015-2-10 下午2:41:02 - * - * @return boolean * @param refresh * @return + * @author 火蚁 2015-2-10 下午2:41:02 */ protected boolean isReadCacheData(boolean refresh) { String key = getCacheKey(); @@ -249,21 +276,19 @@ public abstract class BaseListFragment extends BaseFragment // 是否到时间去刷新数据了 private boolean onTimeRefresh() { - String lastRefreshTime = AppContext.getLastRefreshTime(getCacheKey()); - String currTime = StringUtils.getCurTimeStr(); + String lastRefreshTime = "0";//AppContext.getLastRefreshTime(getCacheKey()); + String currTime = StringUtils.getCurrentTimeStr(); long diff = StringUtils.calDateDifferent(lastRefreshTime, currTime); return needAutoRefresh() && diff > getAutoRefreshTime(); } /*** * 自动刷新的时间 - * + *

    * 默认:自动刷新的时间为半天时间 * - * @author 火蚁 2015-2-9 下午5:55:11 - * - * @return long * @return + * @author 火蚁 2015-2-9 下午5:55:11 */ protected long getAutoRefreshTime() { return 12 * 60 * 60; @@ -277,7 +302,8 @@ public abstract class BaseListFragment extends BaseFragment } } - protected void sendRequestData() {} + protected void sendRequestData() { + } private void readCacheData(String cacheKey) { cancelReadCacheTask(); @@ -291,80 +317,6 @@ public abstract class BaseListFragment extends BaseFragment } } - private class CacheTask extends AsyncTask> { - private final WeakReference mContext; - - private CacheTask(Context context) { - mContext = new WeakReference(context); - } - - @Override - protected ListEntity doInBackground(String... params) { - Serializable seri = CacheManager.readObject(mContext.get(), - params[0]); - if (seri == null) { - return null; - } else { - return readList(seri); - } - } - - @Override - protected void onPostExecute(ListEntity list) { - super.onPostExecute(list); - if (list != null) { - executeOnLoadDataSuccess(list.getList()); - } else { - executeOnLoadDataError(null); - } - executeOnLoadFinish(); - } - } - - private class SaveCacheTask extends AsyncTask { - private final WeakReference mContext; - private final Serializable seri; - private final String key; - - private SaveCacheTask(Context context, Serializable seri, String key) { - mContext = new WeakReference(context); - this.seri = seri; - this.key = key; - } - - @Override - protected Void doInBackground(Void... params) { - CacheManager.saveObject(mContext.get(), seri, key); - return null; - } - } - - protected AsyncHttpResponseHandler mHandler = new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int statusCode, Header[] headers, - byte[] responseBytes) { - if (mCurrentPage == 0 && needAutoRefresh()) { - AppContext.putToLastRefreshTime(getCacheKey(), - StringUtils.getCurTimeStr()); - } - if (isAdded()) { - if (mState == STATE_REFRESH) { - onRefreshNetworkSuccess(); - } - executeParserTask(responseBytes); - } - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) { - if (isAdded()) { - readCacheData(getCacheKey()); - } - } - }; - protected void executeOnLoadDataSuccess(List data) { if (data == null) { data = new ArrayList(); @@ -373,7 +325,7 @@ public abstract class BaseListFragment extends BaseFragment if (mResult != null && !mResult.OK()) { AppContext.showToast(mResult.getErrorMessage()); // 注销登陆,密码已经修改,cookie,失效了 - AppContext.getInstance().Logout(); + //AppContext.getInstance().Logout(); } mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); @@ -415,7 +367,6 @@ public abstract class BaseListFragment extends BaseFragment * 是否需要隐藏listview,显示无数据状态 * * @author 火蚁 2015-1-27 下午6:18:59 - * */ protected boolean needShowEmptyNoData() { return true; @@ -437,7 +388,8 @@ public abstract class BaseListFragment extends BaseFragment return AppContext.PAGE_SIZE; } - protected void onRefreshNetworkSuccess() {} + protected void onRefreshNetworkSuccess() { + } protected void executeOnLoadDataError(String error) { if (mCurrentPage == 0 @@ -445,9 +397,9 @@ public abstract class BaseListFragment extends BaseFragment mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); } else { - //在无网络时,滚动到底部时,mCurrentPage先自加了,然而在失败时却 - //没有减回来,如果刻意在无网络的情况下上拉,可以出现漏页问题 - //find by TopJohn + //在无网络时,滚动到底部时,mCurrentPage先自加了,然而在失败时却 + //没有减回来,如果刻意在无网络的情况下上拉,可以出现漏页问题 + //find by TopJohn mCurrentPage--; mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); @@ -462,7 +414,9 @@ public abstract class BaseListFragment extends BaseFragment mState = STATE_NONE; } - /** 设置顶部正在加载的状态 */ + /** + * 设置顶部正在加载的状态 + */ protected void setSwipeRefreshLoadingState() { if (mSwipeRefreshLayout != null) { mSwipeRefreshLayout.setRefreshing(true); @@ -471,7 +425,9 @@ public abstract class BaseListFragment extends BaseFragment } } - /** 设置顶部加载完毕的状态 */ + /** + * 设置顶部加载完毕的状态 + */ protected void setSwipeRefreshLoadedState() { if (mSwipeRefreshLayout != null) { mSwipeRefreshLayout.setRefreshing(false); @@ -492,50 +448,6 @@ public abstract class BaseListFragment extends BaseFragment } } - class ParserTask extends AsyncTask { - - private final byte[] reponseData; - private boolean parserError; - private List list; - - public ParserTask(byte[] data) { - this.reponseData = data; - } - - @Override - protected String doInBackground(Void... params) { - try { - ListEntity data = parseList(new ByteArrayInputStream( - reponseData)); - new SaveCacheTask(getActivity(), data, getCacheKey()).execute(); - list = data.getList(); - if (list == null) { - ResultBean resultBean = XmlUtils.toBean(ResultBean.class, - reponseData); - if (resultBean != null) { - mResult = resultBean.getResult(); - } - } - } catch (Exception e) { - e.printStackTrace(); - - parserError = true; - } - return null; - } - - @Override - protected void onPostExecute(String result) { - super.onPostExecute(result); - if (parserError) { - readCacheData(getCacheKey()); - } else { - executeOnLoadDataSuccess(list); - executeOnLoadFinish(); - } - } - } - @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (mAdapter == null || mAdapter.getCount() == 0) { @@ -568,7 +480,7 @@ public abstract class BaseListFragment extends BaseFragment @Override public void onScroll(AbsListView view, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { + int visibleItemCount, int totalItemCount) { // 数据已经全部加载,或数据为空时,或正在加载,不处理滚动事件 // if (mState == STATE_NOMORE || mState == STATE_LOADMORE // || mState == STATE_REFRESH) { @@ -589,18 +501,85 @@ public abstract class BaseListFragment extends BaseFragment /** * 保存已读的文章列表 - * - * @param view - * @param prefFileName - * @param key */ protected void saveToReadedList(final View view, final String prefFileName, - final String key) { - // 放入已读列表 - AppContext.putReadedPostList(prefFileName, key, "true"); + final String key) { TextView tvTitle = (TextView) view.findViewById(R.id.tv_title); if (tvTitle != null) { tvTitle.setTextColor(AppContext.getInstance().getResources().getColor(ThemeSwitchUtils.getTitleReadedColor())); } } + + private class CacheTask extends AsyncTask> { + private final WeakReference mContext; + + private CacheTask(Context context) { + mContext = new WeakReference(context); + } + + @Override + protected ListEntity doInBackground(String... params) { + Serializable seri = CacheManager.readObject(mContext.get(), + params[0]); + if (seri == null) { + return null; + } else { + return readList(seri); + } + } + + @Override + protected void onPostExecute(ListEntity list) { + super.onPostExecute(list); + if (list != null) { + executeOnLoadDataSuccess(list.getList()); + } else { + executeOnLoadDataError(null); + } + executeOnLoadFinish(); + } + } + + class ParserTask extends AsyncTask { + + private final byte[] reponseData; + private boolean parserError; + private List list; + + public ParserTask(byte[] data) { + this.reponseData = data; + } + + @Override + protected String doInBackground(Void... params) { + try { + final ListEntity data = parseList(new ByteArrayInputStream( + reponseData)); + CacheManager.saveObject(getActivity(), data, getCacheKey()); + list = data.getList(); + if (list == null) { + ResultBean resultBean = XmlUtils.toBean(ResultBean.class, + reponseData); + if (resultBean != null) { + mResult = resultBean.getResult(); + } + } + } catch (Exception e) { + e.printStackTrace(); + parserError = true; + } + return null; + } + + @Override + protected void onPostExecute(String result) { + super.onPostExecute(result); + if (parserError) { + readCacheData(getCacheKey()); + } else { + executeOnLoadDataSuccess(list); + executeOnLoadFinish(); + } + } + } } diff --git a/app/src/main/java/net/oschina/app/base/BaseNewActivity.java b/app/src/main/java/net/oschina/app/base/BaseNewActivity.java deleted file mode 100644 index 77a7283aebf500fd0e0cf35dcef8f5fd92bb2f44..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/base/BaseNewActivity.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.oschina.app.base; - -import android.support.v7.app.AppCompatActivity; - -/** - * Created by 火蚁 on 15/5/18. - */ -public class BaseNewActivity extends AppCompatActivity { - - -} diff --git a/app/src/main/java/net/oschina/app/base/BaseViewPagerFragment.java b/app/src/main/java/net/oschina/app/base/BaseViewPagerFragment.java index f7b55f2c5c6ebc0866603f38d7c3c5a3a6b0cd52..19e564d0b90e14b9791eda3f9660790cd3fcf959 100644 --- a/app/src/main/java/net/oschina/app/base/BaseViewPagerFragment.java +++ b/app/src/main/java/net/oschina/app/base/BaseViewPagerFragment.java @@ -1,62 +1,87 @@ package net.oschina.app.base; -import net.oschina.app.R; -import net.oschina.app.adapter.ViewPageFragmentAdapter; -import net.oschina.app.ui.empty.EmptyLayout; -import net.oschina.app.widget.PagerSlidingTabStrip; import android.os.Bundle; import android.support.v4.view.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import net.oschina.app.R; +import net.oschina.app.adapter.ViewPageFragmentAdapter; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.widget.PagerSlidingTabStrip; + /** * 带有导航条的基类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年11月6日 下午4:59:50 - * */ public abstract class BaseViewPagerFragment extends BaseFragment { + private static final String TAG = "BaseViewPagerFragment"; protected PagerSlidingTabStrip mTabStrip; protected ViewPager mViewPager; protected ViewPageFragmentAdapter mTabsAdapter; protected EmptyLayout mErrorLayout; + protected View mRoot; + + @Override + public void onDestroyView() { + super.onDestroyView(); + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - return inflater.inflate(R.layout.base_viewpage_fragment, null); + Bundle savedInstanceState) { + if (mRoot == null) { + View root = inflater.inflate(R.layout.base_viewpage_fragment, null); + + mTabStrip = (PagerSlidingTabStrip) root + .findViewById(R.id.pager_tabstrip); + + mViewPager = (ViewPager) root.findViewById(R.id.pager); + + mErrorLayout = (EmptyLayout) root.findViewById(R.id.error_layout); + + mTabsAdapter = new ViewPageFragmentAdapter(getChildFragmentManager(), + mTabStrip, mViewPager); + setScreenPageLimit(); + mRoot = root; + onSetupTabAdapter(mTabsAdapter); + } + return mRoot; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { + /* mTabStrip = (PagerSlidingTabStrip) view .findViewById(R.id.pager_tabstrip); - mViewPager = (ViewPager) view.findViewById(R.id.pager); + mBannerView = (ViewPager) view.findViewById(R.id.pager); mErrorLayout = (EmptyLayout) view.findViewById(R.id.error_layout); mTabsAdapter = new ViewPageFragmentAdapter(getChildFragmentManager(), - mTabStrip, mViewPager); + mTabStrip, mBannerView); setScreenPageLimit(); onSetupTabAdapter(mTabsAdapter); - // if (savedInstanceState != null) { - // int pos = savedInstanceState.getInt("position"); - // mViewPager.setCurrentItem(pos, true); - // } + */ + if (savedInstanceState != null) { + int pos = savedInstanceState.getInt("position"); + mViewPager.setCurrentItem(pos, true); + } } - + protected void setScreenPageLimit() { } // @Override // public void onSaveInstanceState(Bundle outState) { // //No call for super(). Bug on API Level > 11. - // if (outState != null && mViewPager != null) { - // outState.putInt("position", mViewPager.getCurrentItem()); + // if (outState != null && mBannerView != null) { + // outState.putInt("position", mBannerView.getCurrentItem()); // } // //super.onSaveInstanceState(outState); // } diff --git a/app/src/main/java/net/oschina/app/base/BeseHaveHeaderListFragment.java b/app/src/main/java/net/oschina/app/base/BeseHaveHeaderListFragment.java index b4a85e125d2e7fed6b9705708daffbd07338cf25..1462e9d8d4dcd51d39fefab8741b0c7e675a432d 100644 --- a/app/src/main/java/net/oschina/app/base/BeseHaveHeaderListFragment.java +++ b/app/src/main/java/net/oschina/app/base/BeseHaveHeaderListFragment.java @@ -1,33 +1,31 @@ package net.oschina.app.base; -import java.io.ByteArrayInputStream; -import java.io.Serializable; -import java.lang.ref.WeakReference; - -import net.oschina.app.bean.Entity; -import net.oschina.app.cache.CacheManager; -import net.oschina.app.ui.empty.EmptyLayout; - -import cz.msebera.android.httpclient.Header; - import android.app.Activity; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; -import butterknife.ButterKnife; import com.loopj.android.http.AsyncHttpResponseHandler; +import net.oschina.app.bean.Entity; +import net.oschina.app.cache.CacheManager; +import net.oschina.app.ui.empty.EmptyLayout; + +import java.io.ByteArrayInputStream; +import java.io.Serializable; +import java.lang.ref.WeakReference; + +import butterknife.ButterKnife; +import cz.msebera.android.httpclient.Header; + /** * 需要加入header的BaseListFragment - * - * @desc 应用场景:如动弹详情、团队任务详情这些, 即是头部显示详情,然后下面显示评论列表的 - * - * BeseHaveHeaderListFragment.java - * + * * @author 火蚁(http://my.oschina.net/u/253900) - * + * @desc 应用场景:如动弹详情、团队任务详情这些, 即是头部显示详情,然后下面显示评论列表的 + *

    + * BeseHaveHeaderListFragment.java * @data 2015-1-27 下午3:02:42 */ public abstract class BeseHaveHeaderListFragment @@ -63,7 +61,7 @@ public abstract class BeseHaveHeaderListFragment extends BaseF private AsyncTask mCacheTask; + private ShareDialog mDialog; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -84,7 +86,7 @@ public abstract class CommonDetailFragment extends BaseF mCommentCount = getActivity().getIntent().getIntExtra("comment_count", 0); mId = getActivity().getIntent().getIntExtra("id", 0); - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); initView(view); initData(); requestData(false); @@ -117,6 +119,7 @@ public abstract class CommonDetailFragment extends BaseF @Override public void onDestroyView() { recycleWebView(); + mDialog = null; super.onDestroyView(); } @@ -187,7 +190,7 @@ public abstract class CommonDetailFragment extends BaseF if (seri == null) { return null; } else { - return (T)seri; + return (T) seri; } } return null; @@ -218,17 +221,19 @@ public abstract class CommonDetailFragment extends BaseF return; } - mWebView.loadDataWithBaseURL("", this.getWebViewBody(detail), "text/html", "UTF-8", ""); - // 显示存储的字体大小 - mWebView.loadUrl(FontSizeUtils.getSaveFontSize()); - boolean favoriteState = getFavoriteState() == 1; - setFavoriteState(favoriteState); - - // 判断最新的评论数是否大于 - if (getCommentCount() > mCommentCount) { - mCommentCount = getCommentCount(); + if (mWebView != null) { + mWebView.loadDataWithBaseURL("", this.getWebViewBody(detail), "text/html", "UTF-8", ""); + // 显示存储的字体大小 + mWebView.loadUrl(FontSizeUtils.getSaveFontSize()); + boolean favoriteState = getFavoriteState() == 1; + setFavoriteState(favoriteState); + + // 判断最新的评论数是否大于 + if (getCommentCount() > mCommentCount) { + mCommentCount = getCommentCount(); + } + setCommentCount(mCommentCount); } - setCommentCount(mCommentCount); } protected void executeOnLoadDataError() { @@ -295,7 +300,7 @@ public abstract class CommonDetailFragment extends BaseF final String[] items = getResources().getStringArray( R.array.font_size); - fontSizeChange = DialogHelp.getSingleChoiceDialog(getActivity(), items, FontSizeUtils.getSaveFontSizeIndex(), new DialogInterface.OnClickListener() { + fontSizeChange = DialogHelper.getSingleChoiceDialog(getActivity(), "", items, FontSizeUtils.getSaveFontSizeIndex(), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { // 更改字体大小 @@ -315,11 +320,11 @@ public abstract class CommonDetailFragment extends BaseF AppContext.showToastShort(R.string.tip_no_internet); return; } - if (!AppContext.getInstance().isLogin()) { + if (!AccountHelper.isLogin()) { UIHelper.showLoginActivity(getActivity()); return; } - int uid = AppContext.getInstance().getLoginUid(); + int uid = (int) AccountHelper.getUserId(); final boolean isFavorited = getFavoriteState() == 1 ? true : false; AsyncHttpResponseHandler mFavoriteHandler = new AsyncHttpResponseHandler() { @@ -381,7 +386,7 @@ public abstract class CommonDetailFragment extends BaseF if (mId == 0 || mDetail == null) { AppContext.showToast("正在加载,请稍等..."); } - if (!AppContext.getInstance().isLogin()) { + if (!AccountHelper.isLogin()) { UIHelper.showLoginActivity(getActivity()); return; } @@ -392,7 +397,7 @@ public abstract class CommonDetailFragment extends BaseF dialog.setCancelable(true); dialog.setTitle(R.string.report); dialog.setCanceledOnTouchOutside(true); - dialog.setNegativeButton(R.string.cancle, null); + dialog.setNegativeButton(R.string.cancel, null); final TextHttpResponseHandler mReportHandler = new TextHttpResponseHandler() { @Override @@ -407,7 +412,7 @@ public abstract class CommonDetailFragment extends BaseF @Override public void onFailure(int arg0, Header[] arg1, String arg2, Throwable arg3) { - AppContext.showToastShort(R.string.tip_report_faile); + AppContext.showToastShort(R.string.tip_report_failed); } @Override @@ -430,6 +435,7 @@ public abstract class CommonDetailFragment extends BaseF }); dialog.show(); } + // 分享 public void handleShare() { if (mDetail == null || TextUtils.isEmpty(getShareContent()) @@ -437,13 +443,15 @@ public abstract class CommonDetailFragment extends BaseF AppContext.showToast("内容加载失败..."); return; } - final ShareDialog dialog = new ShareDialog(getActivity()); - dialog.setCancelable(true); - dialog.setCanceledOnTouchOutside(true); - dialog.setTitle(R.string.share_to); - dialog.setShareInfo(getShareTitle(), getShareContent(), getShareUrl()); - dialog.show(); + if (mDialog == null) + mDialog = new ShareDialog(getActivity()); + mDialog.setCancelable(true); + mDialog.setCanceledOnTouchOutside(true); + mDialog.setTitle(R.string.share_to); + mDialog.setShareInfo(getShareTitle(), getShareContent(), getShareUrl()); + mDialog.show(); } + // 显示评论列表 public void onCilckShowComment() { showCommentView(); @@ -488,7 +496,9 @@ public abstract class CommonDetailFragment extends BaseF @Override public void onFinish() { ((DetailActivity) getActivity()).emojiFragment.hideAllKeyBoard(); - }; + } + + ; }; // 发表评论 @@ -498,7 +508,7 @@ public abstract class CommonDetailFragment extends BaseF AppContext.showToastShort(R.string.tip_network_error); return; } - if (!AppContext.getInstance().isLogin()) { + if (!AccountHelper.isLogin()) { UIHelper.showLoginActivity(getActivity()); return; } @@ -508,8 +518,7 @@ public abstract class CommonDetailFragment extends BaseF } showWaitDialog(R.string.progress_submit); - OSChinaApi.publicComment(getCommentType(), mId, AppContext - .getInstance().getLoginUid(), str.toString(), 0, + OSChinaApi.publicComment(getCommentType(), mId, (int) AccountHelper.getUserId(), str.toString(), 0, mCommentHandler); } @@ -532,27 +541,48 @@ public abstract class CommonDetailFragment extends BaseF // 获取缓存的key protected abstract String getCacheKey(); + // 从网络中读取数据 protected abstract void sendRequestDataForNet(); + // 解析数据 protected abstract T parseData(InputStream is); + // 返回填充到webview中的内容 protected abstract String getWebViewBody(T detail); + // 显示评论列表 protected abstract void showCommentView(); + // 获取评论的类型 protected abstract int getCommentType(); + protected abstract String getShareTitle(); + protected abstract String getShareContent(); + protected abstract String getShareUrl(); + // 返回举报的url - protected String getRepotrUrl() {return "";} + protected String getRepotrUrl() { + return ""; + } + // 返回举报的类型 - protected byte getReportType() {return Report.TYPE_QUESTION;} + protected byte getReportType() { + return Report.TYPE_QUESTION; + } // 获取收藏类型(如新闻、博客、帖子) protected abstract int getFavoriteTargetType(); + protected abstract int getFavoriteState(); + protected abstract void updateFavoriteChanged(int newFavoritedState); + protected abstract int getCommentCount(); + + public ShareDialog getDialog() { + return mDialog; + } } diff --git a/app/src/main/java/net/oschina/app/base/ListBaseAdapter.java b/app/src/main/java/net/oschina/app/base/ListBaseAdapter.java index 4468bc4c74de089e8a029212fd3d4bf2f6f67e54..ff37c728bf2476becf41aba84832b683fdc0ea70 100644 --- a/app/src/main/java/net/oschina/app/base/ListBaseAdapter.java +++ b/app/src/main/java/net/oschina/app/base/ListBaseAdapter.java @@ -1,7 +1,9 @@ package net.oschina.app.base; + import android.annotation.SuppressLint; import android.content.Context; +import android.support.annotation.DrawableRes; import android.text.Html; import android.text.Spanned; import android.text.TextUtils; @@ -9,6 +11,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; @@ -43,6 +46,12 @@ public class ListBaseAdapter extends BaseAdapter { private LayoutInflater mInflater; + protected Context mContext; + + public ListBaseAdapter(Context context) { + mContext = context; + } + protected LayoutInflater getLayoutInflater(Context context) { if (mInflater == null) { mInflater = (LayoutInflater) context @@ -91,8 +100,8 @@ public class ListBaseAdapter extends BaseAdapter { return getDataSize(); } - public int getDataSizePlus1(){ - if(hasFooterView()){ + public int getDataSizePlus1() { + if (hasFooterView()) { return getDataSize() + 1; } return getDataSize(); @@ -175,7 +184,7 @@ public class ListBaseAdapter extends BaseAdapter { @SuppressLint("InflateParams") @Override public View getView(int position, View convertView, ViewGroup parent) { - if (position == getCount() - 1&&hasFooterView()) {// 最后一条 + if (position == getCount() - 1 && hasFooterView()) {// 最后一条 // if (position < _data.size()) { // position = getCount() - 2; // footview // } @@ -192,35 +201,35 @@ public class ListBaseAdapter extends BaseAdapter { .findViewById(R.id.progressbar); TextView text = (TextView) mFooterView.findViewById(R.id.text); switch (getState()) { - case STATE_LOAD_MORE: - setFooterViewLoading(); - break; - case STATE_NO_MORE: - mFooterView.setVisibility(View.VISIBLE); - progress.setVisibility(View.GONE); - text.setVisibility(View.VISIBLE); - text.setText(_loadFinishText); - break; - case STATE_EMPTY_ITEM: - progress.setVisibility(View.GONE); - mFooterView.setVisibility(View.VISIBLE); - text.setText(_noDateText); - break; - case STATE_NETWORK_ERROR: - mFooterView.setVisibility(View.VISIBLE); - progress.setVisibility(View.GONE); - text.setVisibility(View.VISIBLE); - if (TDevice.hasInternet()) { - text.setText("加载出错了"); - } else { - text.setText("没有可用的网络"); - } - break; - default: - progress.setVisibility(View.GONE); - mFooterView.setVisibility(View.GONE); - text.setVisibility(View.GONE); - break; + case STATE_LOAD_MORE: + setFooterViewLoading(); + break; + case STATE_NO_MORE: + mFooterView.setVisibility(View.VISIBLE); + progress.setVisibility(View.GONE); + text.setVisibility(View.VISIBLE); + text.setText(_loadFinishText); + break; + case STATE_EMPTY_ITEM: + progress.setVisibility(View.GONE); + mFooterView.setVisibility(View.VISIBLE); + text.setText(_noDateText); + break; + case STATE_NETWORK_ERROR: + mFooterView.setVisibility(View.VISIBLE); + progress.setVisibility(View.GONE); + text.setVisibility(View.VISIBLE); + if (TDevice.hasInternet()) { + text.setText("加载出错了"); + } else { + text.setText("没有可用的网络"); + } + break; + default: + progress.setVisibility(View.GONE); + mFooterView.setVisibility(View.GONE); + text.setVisibility(View.GONE); + break; } return mFooterView; } @@ -237,7 +246,7 @@ public class ListBaseAdapter extends BaseAdapter { private LinearLayout mFooterView; - protected boolean hasFooterView(){ + protected boolean hasFooterView() { return true; } @@ -295,6 +304,11 @@ public class ListBaseAdapter extends BaseAdapter { } } + + protected void setImageRes(ImageView imageRes, @DrawableRes int resId) { + imageRes.setImageResource(resId); + } + protected void setText(TextView textView, String text) { setText(textView, text, false); } diff --git a/app/src/main/java/net/oschina/app/bean/Active.java b/app/src/main/java/net/oschina/app/bean/Active.java index 0bce263dee26ebef44e6a3d4694bd92ba6bc18c1..53de61072c5ed870b51c9f10ed5d9d289db8b9b7 100644 --- a/app/src/main/java/net/oschina/app/bean/Active.java +++ b/app/src/main/java/net/oschina/app/bean/Active.java @@ -1,240 +1,239 @@ package net.oschina.app.bean; -import java.io.Serializable; - import com.thoughtworks.xstream.annotations.XStreamAlias; +import java.io.Serializable; + /** * 动态实体类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年10月22日 下午3:22:09 - * */ @SuppressWarnings("serial") @XStreamAlias("active") public class Active extends Entity { - public final static int CATALOG_OTHER = 0;// 其他 - public final static int CATALOG_NEWS = 1;// 新闻 - public final static int CATALOG_POST = 2;// 帖子 - public final static int CATALOG_TWEET = 3;// 动弹 - public final static int CATALOG_BLOG = 4;// 博客 + public final static int CATALOG_OTHER = 0;// 其他 + public final static int CATALOG_NEWS = 1;// 新闻 + public final static int CATALOG_POST = 2;// 帖子 + public final static int CATALOG_TWEET = 3;// 动弹 + public final static int CATALOG_BLOG = 4;// 博客 + + public final static int CLIENT_MOBILE = 2; + public final static int CLIENT_ANDROID = 3; + public final static int CLIENT_IPHONE = 4; + public final static int CLIENT_WINDOWS_PHONE = 5; + + @XStreamAlias("portrait") + private String portrait; - public final static int CLIENT_MOBILE = 2; - public final static int CLIENT_ANDROID = 3; - public final static int CLIENT_IPHONE = 4; - public final static int CLIENT_WINDOWS_PHONE = 5; - - @XStreamAlias("portrait") - private String portrait; + @XStreamAlias("message") + private String message; - @XStreamAlias("message") - private String message; + @XStreamAlias("author") + private String author; - @XStreamAlias("author") - private String author; + @XStreamAlias("authorid") + private int authorId; - @XStreamAlias("authorid") - private int authorId; + @XStreamAlias("activetype") + private int activeType; - @XStreamAlias("activetype") - private int activeType; + @XStreamAlias("objectID") + private int objectId; - @XStreamAlias("objectID") - private int objectId; + @XStreamAlias("catalog") + private int catalog; - @XStreamAlias("catalog") - private int catalog; + @XStreamAlias("objecttype") + private int objectType; - @XStreamAlias("objecttype") - private int objectType; + @XStreamAlias("objectcatalog") + private int objectCatalog; - @XStreamAlias("objectcatalog") - private int objectCatalog; + @XStreamAlias("objecttitle") + private String objectTitle; - @XStreamAlias("objecttitle") - private String objectTitle; + @XStreamAlias("objectreply") + private ObjectReply objectReply; - @XStreamAlias("objectreply") - private ObjectReply objectReply; + @XStreamAlias("commentCount") + private int commentCount; - @XStreamAlias("commentCount") - private int commentCount; + @XStreamAlias("pubDate") + private String pubDate; - @XStreamAlias("pubDate") - private String pubDate; + @XStreamAlias("tweetimage") + private String tweetimage; - @XStreamAlias("tweetimage") - private String tweetimage; - - @XStreamAlias("tweetattach") - private String tweetattach; + @XStreamAlias("tweetattach") + private String tweetattach; - @XStreamAlias("appclient") - private int appClient; + @XStreamAlias("appclient") + private int appClient; - @XStreamAlias("url") - private String url; + @XStreamAlias("url") + private String url; - public String getPortrait() { - return portrait; - } + public String getPortrait() { + return portrait; + } - public void setPortrait(String portrait) { - this.portrait = portrait; - } + public void setPortrait(String portrait) { + this.portrait = portrait; + } - public String getMessage() { - return message; - } + public String getMessage() { + return message; + } - public void setMessage(String message) { - this.message = message; - } + public void setMessage(String message) { + this.message = message; + } - public String getAuthor() { - return author; - } + public String getAuthor() { + return author; + } - public void setAuthor(String author) { - this.author = author; - } + public void setAuthor(String author) { + this.author = author; + } - public int getAuthorId() { - return authorId; - } + public int getAuthorId() { + return authorId; + } - public void setAuthorId(int authorId) { - this.authorId = authorId; - } + public void setAuthorId(int authorId) { + this.authorId = authorId; + } - public int getActiveType() { - return activeType; - } + public int getActiveType() { + return activeType; + } - public void setActiveType(int activeType) { - this.activeType = activeType; - } + public void setActiveType(int activeType) { + this.activeType = activeType; + } - public int getObjectId() { - return objectId; - } + public int getObjectId() { + return objectId; + } - public void setObjectId(int objectId) { - this.objectId = objectId; - } + public void setObjectId(int objectId) { + this.objectId = objectId; + } - public int getCatalog() { - return catalog; - } + public int getCatalog() { + return catalog; + } - public void setCatalog(int catalog) { - this.catalog = catalog; - } + public void setCatalog(int catalog) { + this.catalog = catalog; + } - public int getObjectType() { - return objectType; - } + public int getObjectType() { + return objectType; + } - public void setObjectType(int objectType) { - this.objectType = objectType; - } + public void setObjectType(int objectType) { + this.objectType = objectType; + } - public int getObjectCatalog() { - return objectCatalog; - } + public int getObjectCatalog() { + return objectCatalog; + } - public void setObjectCatalog(int objectCatalog) { - this.objectCatalog = objectCatalog; - } + public void setObjectCatalog(int objectCatalog) { + this.objectCatalog = objectCatalog; + } - public String getObjectTitle() { - return objectTitle; - } + public String getObjectTitle() { + return objectTitle; + } - public void setObjectTitle(String objectTitle) { - this.objectTitle = objectTitle; - } + public void setObjectTitle(String objectTitle) { + this.objectTitle = objectTitle; + } - public ObjectReply getObjectReply() { - return objectReply; - } + public ObjectReply getObjectReply() { + return objectReply; + } - public void setObjectReply(ObjectReply objectReply) { - this.objectReply = objectReply; - } + public void setObjectReply(ObjectReply objectReply) { + this.objectReply = objectReply; + } - public int getCommentCount() { - return commentCount; - } + public int getCommentCount() { + return commentCount; + } - public void setCommentCount(int commentCount) { - this.commentCount = commentCount; - } + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } - public String getPubDate() { - return pubDate; - } + public String getPubDate() { + return pubDate; + } - public void setPubDate(String pubDate) { - this.pubDate = pubDate; - } + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } - public String getTweetattach() { - return tweetattach; - } + public String getTweetattach() { + return tweetattach; + } - public void setTweetattach(String tweetattach) { - this.tweetattach = tweetattach; - } + public void setTweetattach(String tweetattach) { + this.tweetattach = tweetattach; + } - public String getTweetimage() { - return tweetimage; - } + public String getTweetimage() { + return tweetimage; + } - public void setTweetimage(String tweetimage) { - this.tweetimage = tweetimage; - } + public void setTweetimage(String tweetimage) { + this.tweetimage = tweetimage; + } - public int getAppClient() { - return appClient; - } + public int getAppClient() { + return appClient; + } - public void setAppClient(int appClient) { - this.appClient = appClient; - } + public void setAppClient(int appClient) { + this.appClient = appClient; + } - public String getUrl() { - return url; - } + public String getUrl() { + return url; + } - public void setUrl(String url) { - this.url = url; - } + public void setUrl(String url) { + this.url = url; + } - @XStreamAlias("objectreply") - public static class ObjectReply implements Serializable { - @XStreamAlias("objectname") - public String objectName; - - @XStreamAlias("objectbody") - public String objectBody; + @XStreamAlias("objectreply") + public static class ObjectReply implements Serializable { + @XStreamAlias("objectname") + public String objectName; + + @XStreamAlias("objectbody") + public String objectBody; - public String getObjectName() { - return objectName; - } + public String getObjectName() { + return objectName; + } - public void setObjectName(String objectName) { - this.objectName = objectName; - } + public void setObjectName(String objectName) { + this.objectName = objectName; + } - public String getObjectBody() { - return objectBody; - } + public String getObjectBody() { + return objectBody; + } - public void setObjectBody(String objectBody) { - this.objectBody = objectBody; - } - } + public void setObjectBody(String objectBody) { + this.objectBody = objectBody; + } + } } diff --git a/app/src/main/java/net/oschina/app/bean/ActiveList.java b/app/src/main/java/net/oschina/app/bean/ActiveList.java deleted file mode 100644 index 6b2219d0c6417335b9eae4e74fe66d821653c28c..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/ActiveList.java +++ /dev/null @@ -1,60 +0,0 @@ -package net.oschina.app.bean; - -import java.util.ArrayList; -import java.util.List; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 动态实体列表 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年10月22日 下午3:34:21 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class ActiveList extends Entity implements ListEntity { - - public final static int CATALOG_LASTEST = 1;// 最新 - public final static int CATALOG_ATME = 2;// @我 - public final static int CATALOG_COMMENT = 3;// 评论 - public final static int CATALOG_MYSELF = 4;// 我自己 - - @XStreamAlias("pagesize") - private int pageSize; - - @XStreamAlias("activeCount") - private int activeCount; - - @XStreamAlias("activies") - private List activelist = new ArrayList(); - - @XStreamAlias("result") - private Result result; - - public int getPageSize() { - return pageSize; - } - - public int getActiveCount() { - return activeCount; - } - - public List getActivelist() { - return activelist; - } - - @Override - public List getList() { - return activelist; - } - - public Result getResult() { - return result; - } - - public void setResult(Result result) { - this.result = result; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/Apply.java b/app/src/main/java/net/oschina/app/bean/Apply.java index 366bbc7e65bf1951c966af29d102520f47082cb8..3430b35be8492e598754eecd1d166ff5fdb9f88e 100644 --- a/app/src/main/java/net/oschina/app/bean/Apply.java +++ b/app/src/main/java/net/oschina/app/bean/Apply.java @@ -4,58 +4,67 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 活动报名者实体类 + * * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2015年1月16日 下午3:27:04 - * + * @version 创建时间:2015年1月16日 下午3:27:04 */ @SuppressWarnings("serial") @XStreamAlias("apply") public class Apply extends Entity { - - @XStreamAlias("uid") - private int userid; - - @XStreamAlias("name") - private String name; - - @XStreamAlias("portrait") - private String portrait; - - @XStreamAlias("company") - private String company; - - @XStreamAlias("job") - private String job; - - public int getId() { - return userid; - } - public void setId(int userid) { - this.userid = userid; - } - public String getName() { - return name; - } - public void setName(String name) { - this.name = name; - } - public String getPortrait() { - return portrait; - } - public void setPortrait(String portrait) { - this.portrait = portrait; - } - public String getCompany() { - return company; - } - public void setCompany(String company) { - this.company = company; - } - public String getJob() { - return job; - } - public void setJob(String job) { - this.job = job; - } + + @XStreamAlias("uid") + private int userid; + + @XStreamAlias("name") + private String name; + + @XStreamAlias("portrait") + private String portrait; + + @XStreamAlias("company") + private String company; + + @XStreamAlias("job") + private String job; + + public int getId() { + return userid; + } + + public void setId(int userid) { + this.userid = userid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPortrait() { + return portrait; + } + + public void setPortrait(String portrait) { + this.portrait = portrait; + } + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + + public String getJob() { + return job; + } + + public void setJob(String job) { + this.job = job; + } } diff --git a/app/src/main/java/net/oschina/app/bean/Banner.java b/app/src/main/java/net/oschina/app/bean/Banner.java new file mode 100644 index 0000000000000000000000000000000000000000..bb601af9b6cf4e63212436c18ae3e80c43368b17 --- /dev/null +++ b/app/src/main/java/net/oschina/app/bean/Banner.java @@ -0,0 +1,78 @@ +package net.oschina.app.bean; + +/** + * Created by huanghaibin + * on 16-5-23. + */ +public class Banner extends Base { + public static final int BANNER_TYPE_URL = 0;//链接新闻 + public static final int BANNER_TYPE_SOFTWARE = 1;// + public static final int BANNER_TYPE_POST = 2;// + public static final int BANNER_TYPE_BLOG = 3;// + public static final int BANNER_TYPE_TRANSLATEL = 4;// + public static final int BANNER_TYPE_EVENT = 5;// + public static final int BANNER_TYPE_NEWS = 6; + private String name; + private String detail; + private String img; + private String href; + private String pubDate; + private int type; + private long id; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDetail() { + return detail; + } + + public void setDetail(String detail) { + this.detail = detail; + } + + public String getImg() { + return img; + } + + public void setImg(String img) { + this.img = img; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } +} diff --git a/app/src/main/java/net/oschina/app/bean/BarCode.java b/app/src/main/java/net/oschina/app/bean/BarCode.java index c63e5bbb87d212c1cb34956daa9a99c1de30ac83..8a1f76b2d20c035bf8dc30e04c75b704682a117e 100644 --- a/app/src/main/java/net/oschina/app/bean/BarCode.java +++ b/app/src/main/java/net/oschina/app/bean/BarCode.java @@ -1,102 +1,104 @@ package net.oschina.app.bean; -import java.io.Serializable; - import net.oschina.app.AppException; import org.json.JSONException; import org.json.JSONObject; +import java.io.Serializable; + + /** * 二维码扫描实体类 + * * @author 火蚁 (http://my.oschina.net/LittleDY) * @version 1.0 * @created 2014-3-17 */ @SuppressWarnings("serial") -public class BarCode extends Entity implements Serializable{ - - public final static String NODE_REQURE_LOGIN = "require_login"; - public final static String NODE_TYPE = "type"; - public final static String NODE_URL = "url"; - public final static String NODE_TITLE = "title"; - - public final static byte LOGIN_IN = 0x00;// 登录 - public final static byte SIGN_IN = 0x01;// 签到 - - private boolean requireLogin;// 是否需要登录 - private int type;// 类型 - private String url;// url地址 - private String title;// 标题 - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public boolean isRequireLogin() { - return requireLogin; - } - - public void setRequireLogin(boolean requireLogin) { - this.requireLogin = requireLogin; - } - - public int getType() { - return type; - } - - public void setType(int type) { - this.type = type; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public static BarCode parse(String barCodeString) throws AppException { - BarCode barCode = new BarCode(); - try { - // 由字符串创建json对象 - JSONObject jsonObject = new JSONObject(barCodeString); - // 取数据操作 - if (!jsonObject.isNull(NODE_REQURE_LOGIN)) { - barCode.setRequireLogin(jsonObject.getBoolean(NODE_REQURE_LOGIN)); - } else { - barCode.setUrl("www.oschina.net"); - } - if (!jsonObject.isNull(NODE_TYPE)) { - barCode.setType(jsonObject.getInt(NODE_TYPE)); - } else { - barCode.setType(1); - } - if (!jsonObject.isNull(NODE_URL)) { - barCode.setUrl(jsonObject.getString(NODE_URL)); - } else { - barCode.setUrl("www.oschina.net"); - } - if (!jsonObject.isNull(NODE_TITLE)) { - barCode.setTitle(jsonObject.getString(NODE_TITLE)); - } else { - barCode.setTitle(""); - } - } catch (JSONException e) { - // 抛出一个json解析错误的异常 - throw AppException.json(e); - } - return barCode; - } - - @Override - public String toString() { - return "Barcode [requireLogin=" + requireLogin + ", type=" + type - + ", url=" + url + "]"; - } +public class BarCode extends Entity implements Serializable { + + public final static String NODE_REQURE_LOGIN = "require_login"; + public final static String NODE_TYPE = "type"; + public final static String NODE_URL = "url"; + public final static String NODE_TITLE = "title"; + + public final static byte LOGIN_IN = 0x00;// 登录 + public final static byte SIGN_IN = 0x01;// 签到 + + private boolean requireLogin;// 是否需要登录 + private int type;// 类型 + private String url;// url地址 + private String title;// 标题 + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public boolean isRequireLogin() { + return requireLogin; + } + + public void setRequireLogin(boolean requireLogin) { + this.requireLogin = requireLogin; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public static BarCode parse(String barCodeString) throws AppException { + BarCode barCode = new BarCode(); + try { + // 由字符串创建json对象 + JSONObject jsonObject = new JSONObject(barCodeString); + // 取数据操作 + if (!jsonObject.isNull(NODE_REQURE_LOGIN)) { + barCode.setRequireLogin(jsonObject.getBoolean(NODE_REQURE_LOGIN)); + } else { + barCode.setUrl("www.oschina.net"); + } + if (!jsonObject.isNull(NODE_TYPE)) { + barCode.setType(jsonObject.getInt(NODE_TYPE)); + } else { + barCode.setType(1); + } + if (!jsonObject.isNull(NODE_URL)) { + barCode.setUrl(jsonObject.getString(NODE_URL)); + } else { + barCode.setUrl("www.oschina.net"); + } + if (!jsonObject.isNull(NODE_TITLE)) { + barCode.setTitle(jsonObject.getString(NODE_TITLE)); + } else { + barCode.setTitle(""); + } + } catch (JSONException e) { + // 抛出一个json解析错误的异常 + throw AppException.json(e); + } + return barCode; + } + + @Override + public String toString() { + return "Barcode [requireLogin=" + requireLogin + ", type=" + type + + ", url=" + url + "]"; + } } diff --git a/app/src/main/java/net/oschina/app/bean/Base.java b/app/src/main/java/net/oschina/app/bean/Base.java index d9f64576da1ebfd826a3a8283d3cc63d3628da9d..6e6253433fdfb72a5cc8c3f113d690b2b5b76b76 100644 --- a/app/src/main/java/net/oschina/app/bean/Base.java +++ b/app/src/main/java/net/oschina/app/bean/Base.java @@ -1,11 +1,12 @@ package net.oschina.app.bean; -import java.io.Serializable; - import com.thoughtworks.xstream.annotations.XStreamAlias; +import java.io.Serializable; + /** * 实体基类:实现序列化 + * * @author liux (http://my.oschina.net/liux) * @version 1.0 * @created 2012-3-21 @@ -13,14 +14,14 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; @SuppressWarnings("serial") public abstract class Base implements Serializable { - @XStreamAlias("notice") - protected Notice notice; + @XStreamAlias("notice") + protected Notice notice; - public Notice getNotice() { - return notice; - } + public Notice getNotice() { + return notice; + } - public void setNotice(Notice notice) { - this.notice = notice; - } + public void setNotice(Notice notice) { + this.notice = notice; + } } diff --git a/app/src/main/java/net/oschina/app/bean/Blog.java b/app/src/main/java/net/oschina/app/bean/Blog.java index 67eb58d00401f690c35246f56d185f4cd69aea69..6e8f5d721fa726832d5f9156d3e0a3f79c8b6201 100644 --- a/app/src/main/java/net/oschina/app/bean/Blog.java +++ b/app/src/main/java/net/oschina/app/bean/Blog.java @@ -4,123 +4,122 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * @author HuangWenwei - * * @date 2014年9月29日 */ @SuppressWarnings("serial") @XStreamAlias("blog") public class Blog extends Entity { - - public final static int DOC_TYPE_REPASTE = 0;//转帖 - public final static int DOC_TYPE_ORIGINAL = 1;//原创 - - @XStreamAlias("title") - private String title; - @XStreamAlias("url") - private String url; + public final static int DOC_TYPE_REPASTE = 0;//转帖 + public final static int DOC_TYPE_ORIGINAL = 1;//原创 - @XStreamAlias("where") - private String where; + @XStreamAlias("title") + private String title; - @XStreamAlias("commentCount") - private int commentCount; + @XStreamAlias("url") + private String url; - @XStreamAlias("body") - private String body; + @XStreamAlias("where") + private String where; - @XStreamAlias("author") - private String authorname; + @XStreamAlias("commentCount") + private int commentCount; - @XStreamAlias("authorid") - private int authoruid; + @XStreamAlias("body") + private String body; - @XStreamAlias("documentType") - private int documentType; + @XStreamAlias("author") + private String authorname; - @XStreamAlias("pubDate") - private String pubDate; + @XStreamAlias("authorid") + private int authoruid; - @XStreamAlias("favorite") - private int favorite; + @XStreamAlias("documentType") + private int documentType; - public String getTitle() { - return title; - } + @XStreamAlias("pubDate") + private String pubDate; - public void setTitle(String title) { - this.title = title; - } + @XStreamAlias("favorite") + private int favorite; - public String getUrl() { - return url; - } + public String getTitle() { + return title; + } - public void setUrl(String url) { - this.url = url; - } + public void setTitle(String title) { + this.title = title; + } - public String getWhere() { - return where; - } + public String getUrl() { + return url; + } - public void setWhere(String where) { - this.where = where; - } + public void setUrl(String url) { + this.url = url; + } - public int getCommentCount() { - return commentCount; - } + public String getWhere() { + return where; + } - public void setCommentCount(int commentCount) { - this.commentCount = commentCount; - } + public void setWhere(String where) { + this.where = where; + } - public String getBody() { - return body; - } + public int getCommentCount() { + return commentCount; + } - public void setBody(String body) { - this.body = body; - } + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } - public String getAuthor() { - return authorname; - } + public String getBody() { + return body; + } - public void setAuthor(String author) { - this.authorname = author; - } + public void setBody(String body) { + this.body = body; + } - public int getAuthorId() { - return authoruid; - } + public String getAuthor() { + return authorname; + } - public void setAuthorId(int authorId) { - this.authoruid = authorId; - } + public void setAuthor(String author) { + this.authorname = author; + } - public int getDocumenttype() { - return documentType; - } + public int getAuthorId() { + return authoruid; + } - public void setDocumenttype(int documenttype) { - this.documentType = documenttype; - } + public void setAuthorId(int authorId) { + this.authoruid = authorId; + } - public String getPubDate() { - return pubDate; - } + public int getDocumenttype() { + return documentType; + } - public void setPubDate(String pubDate) { - this.pubDate = pubDate; - } + public void setDocumenttype(int documenttype) { + this.documentType = documenttype; + } - public int getFavorite() { - return favorite; - } + public String getPubDate() { + return pubDate; + } - public void setFavorite(int favorite) { - this.favorite = favorite; - } + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getFavorite() { + return favorite; + } + + public void setFavorite(int favorite) { + this.favorite = favorite; + } } diff --git a/app/src/main/java/net/oschina/app/bean/BlogCommentList.java b/app/src/main/java/net/oschina/app/bean/BlogCommentList.java deleted file mode 100644 index c50b632c65526faf220e8d2f5a693f890f440bea..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/BlogCommentList.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.oschina.app.bean; - -import java.util.ArrayList; -import java.util.List; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 博客评论列表实体类 - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年10月17日 上午10:28:04 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class BlogCommentList extends Entity implements ListEntity { - - @XStreamAlias("pagesize") - private int pageSize; - @XStreamAlias("allCount") - private int allCount; - @XStreamAlias("comments") - private List commentlist = new ArrayList(); - - public int getPageSize() { - return pageSize; - } - - public int getAllCount() { - return allCount; - } - - public List getCommentlist() { - return commentlist; - } - - @Override - public List getList() { - return commentlist; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/BlogDetail.java b/app/src/main/java/net/oschina/app/bean/BlogDetail.java deleted file mode 100644 index 97918d7fc6de7bf15262552c727a93d9b633f066..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/BlogDetail.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.oschina.app.bean; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 博客详情 - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年10月15日 上午10:51:11 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class BlogDetail extends Entity { - - @XStreamAlias("blog") - private Blog blog; - - public Blog getBlog() { - return blog; - } - - public void setBlog(Blog blog) { - this.blog = blog; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/BlogList.java b/app/src/main/java/net/oschina/app/bean/BlogList.java deleted file mode 100644 index 80ded41b5e7ccbc37a3ced6b7954f9868d86ac24..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/BlogList.java +++ /dev/null @@ -1,67 +0,0 @@ -package net.oschina.app.bean; - -import java.util.ArrayList; -import java.util.List; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * @author HuangWenwei - * - * @date 2014年9月28日 - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class BlogList extends Entity implements ListEntity { - - public final static String PREF_READED_BLOG_LIST = "readed_blog_list.pref"; - - public static final String CATALOG_LATEST = "latest"; - public static final String CATALOG_RECOMMEND = "recommend"; - - @XStreamAlias("pagesize") - private int pagesize; - - @XStreamAlias("blogs") - private List bloglist = new ArrayList(); - - @XStreamAlias("blogsCount") - private int blogsCount; - - public int getPageSize() { - return pagesize; - } - - public void setPageSize(int pageSize) { - this.pagesize = pageSize; - } - - public List getBloglist() { - return bloglist; - } - - public void setBloglist(List bloglist) { - this.bloglist = bloglist; - } - - @Override - public List getList() { - return bloglist; - } - - public int getPagesize() { - return pagesize; - } - - public void setPagesize(int pagesize) { - this.pagesize = pagesize; - } - - public int getBlogsCount() { - return blogsCount; - } - - public void setBlogsCount(int blogsCount) { - this.blogsCount = blogsCount; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/Comment.java b/app/src/main/java/net/oschina/app/bean/Comment.java index ff846d07a40955eea894a63cad266e4e3e5fb2fc..aead4e945d4081137cad4674c2311a1a5c8da795 100644 --- a/app/src/main/java/net/oschina/app/bean/Comment.java +++ b/app/src/main/java/net/oschina/app/bean/Comment.java @@ -1,287 +1,295 @@ package net.oschina.app.bean; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - import android.os.Parcel; import android.os.Parcelable; import com.thoughtworks.xstream.annotations.XStreamAlias; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + /** * 评论实体类 + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年10月14日 下午3:29:22 - * */ @SuppressWarnings("serial") @XStreamAlias("comment") public class Comment extends Entity implements Parcelable { - public static final String BUNDLE_KEY_COMMENT = "bundle_key_comment"; - public static final String BUNDLE_KEY_ID = "bundle_key_id"; - public static final String BUNDLE_KEY_CATALOG = "bundle_key_catalog"; - public static final String BUNDLE_KEY_BLOG = "bundle_key_blog"; - public static final String BUNDLE_KEY_OPERATION = "bundle_key_operation"; - - public static final int OPT_ADD = 1; - public static final int OPT_REMOVE = 2; - - public final static int CLIENT_MOBILE = 2; - public final static int CLIENT_ANDROID = 3; - public final static int CLIENT_IPHONE = 4; - public final static int CLIENT_WINDOWS_PHONE = 5; - - @XStreamAlias("portrait") - private String portrait; - - @XStreamAlias("content") - private String content; - - @XStreamAlias("author") - private String author; - - @XStreamAlias("authorid") - private int authorId; - - @XStreamAlias("pubDate") - private String pubDate; - - @XStreamAlias("appclient") - private int appClient; - - @XStreamAlias("replies") - private List replies = new ArrayList(); - - @XStreamAlias("refers") - private List refers = new ArrayList(); - - @SuppressWarnings("unchecked") - public Comment(Parcel source) { - id = source.readInt(); - portrait = source.readString(); - author = source.readString(); - authorId = source.readInt(); - pubDate = source.readString(); - appClient = source.readInt(); - content = source.readString(); - - replies = source.readArrayList(Reply.class.getClassLoader()); - refers = source.readArrayList(Refer.class.getClassLoader()); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(id); - dest.writeString(portrait); - dest.writeString(author); - dest.writeInt(authorId); - dest.writeString(pubDate); - dest.writeInt(appClient); - dest.writeString(content); - - dest.writeList(replies); - dest.writeList(refers); - } - - @Override - public int describeContents() { - return 0; - } - - public String getPortrait() { - return portrait; - } - - public void setPortrait(String portrait) { - this.portrait = portrait; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } - - public int getAuthorId() { - return authorId; - } - - public void setAuthorId(int authorId) { - this.authorId = authorId; - } - - public String getPubDate() { - return pubDate; - } - - public void setPubDate(String pubDate) { - this.pubDate = pubDate; - } - - public int getAppClient() { - return appClient; - } - - public void setAppClient(int appClient) { - this.appClient = appClient; - } - - public List getReplies() { - return replies; - } - - public void setReplies(List replies) { - this.replies = replies; - } - - public List getRefers() { - return refers; - } - - public void setRefers(List refers) { - this.refers = refers; - } - - @XStreamAlias("reply") - public static class Reply implements Serializable, Parcelable { - @XStreamAlias("rauthor") - public String rauthor; - @XStreamAlias("rpubDate") - public String rpubDate; - @XStreamAlias("rcontent") - public String rcontent; - - public Reply() { - } - - public Reply(Parcel source) { - rauthor = source.readString(); - rpubDate = source.readString(); - rcontent = source.readString(); - } - - public String getRauthor() { - return rauthor; - } - public void setRauthor(String rauthor) { - this.rauthor = rauthor; - } - public String getRpubDate() { - return rpubDate; - } - public void setRpubDate(String rpubDate) { - this.rpubDate = rpubDate; - } - public String getRcontent() { - return rcontent; - } - public void setRcontent(String rcontent) { - this.rcontent = rcontent; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(rauthor); - dest.writeString(rpubDate); - dest.writeString(rcontent); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator CREATOR = new Creator() { - - @Override - public Reply[] newArray(int size) { - return new Reply[size]; - } - - @Override - public Reply createFromParcel(Parcel source) { - return new Reply(source); - } - }; - - } - - @XStreamAlias("refer") - public static class Refer implements Serializable, Parcelable { - - @XStreamAlias("refertitle") - public String refertitle; - @XStreamAlias("referbody") - public String referbody; - - public Refer() { - } - - public Refer(Parcel source) { - referbody = source.readString(); - refertitle = source.readString(); - } - - public String getRefertitle() { - return refertitle; - } - public void setRefertitle(String refertitle) { - this.refertitle = refertitle; - } - public String getReferbody() { - return referbody; - } - public void setReferbody(String referbody) { - this.referbody = referbody; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(referbody); - dest.writeString(refertitle); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator CREATOR = new Creator() { - - @Override - public Refer[] newArray(int size) { - return new Refer[size]; - } - - @Override - public Refer createFromParcel(Parcel source) { - return new Refer(source); - } - }; - } - - public static final Parcelable.Creator CREATOR = new Creator() { - - @Override - public Comment[] newArray(int size) { - return new Comment[size]; - } - - @Override - public Comment createFromParcel(Parcel source) { - return new Comment(source); - } - }; + public static final String BUNDLE_KEY_COMMENT = "bundle_key_comment"; + public static final String BUNDLE_KEY_ID = "bundle_key_id"; + public static final String BUNDLE_KEY_CATALOG = "bundle_key_catalog"; + public static final String BUNDLE_KEY_BLOG = "bundle_key_blog"; + public static final String BUNDLE_KEY_OPERATION = "bundle_key_operation"; + + public static final int OPT_ADD = 1; + public static final int OPT_REMOVE = 2; + + public final static int CLIENT_MOBILE = 2; + public final static int CLIENT_ANDROID = 3; + public final static int CLIENT_IPHONE = 4; + public final static int CLIENT_WINDOWS_PHONE = 5; + + @XStreamAlias("portrait") + private String portrait; + + @XStreamAlias("content") + private String content; + + @XStreamAlias("author") + private String author; + + @XStreamAlias("authorid") + private int authorId; + + @XStreamAlias("pubDate") + private String pubDate; + + @XStreamAlias("appclient") + private int appClient; + + @XStreamAlias("replies") + private List replies = new ArrayList(); + + @XStreamAlias("refers") + private List refers = new ArrayList(); + + @SuppressWarnings("unchecked") + public Comment(Parcel source) { + id = source.readInt(); + portrait = source.readString(); + author = source.readString(); + authorId = source.readInt(); + pubDate = source.readString(); + appClient = source.readInt(); + content = source.readString(); + + replies = source.readArrayList(Reply.class.getClassLoader()); + refers = source.readArrayList(Refer.class.getClassLoader()); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + dest.writeString(portrait); + dest.writeString(author); + dest.writeInt(authorId); + dest.writeString(pubDate); + dest.writeInt(appClient); + dest.writeString(content); + + dest.writeList(replies); + dest.writeList(refers); + } + + @Override + public int describeContents() { + return 0; + } + + public String getPortrait() { + return portrait; + } + + public void setPortrait(String portrait) { + this.portrait = portrait; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public int getAuthorId() { + return authorId; + } + + public void setAuthorId(int authorId) { + this.authorId = authorId; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getAppClient() { + return appClient; + } + + public void setAppClient(int appClient) { + this.appClient = appClient; + } + + public List getReplies() { + return replies; + } + + public void setReplies(List replies) { + this.replies = replies; + } + + public List getRefers() { + return refers; + } + + public void setRefers(List refers) { + this.refers = refers; + } + + @XStreamAlias("reply") + public static class Reply implements Serializable, Parcelable { + @XStreamAlias("rauthor") + public String rauthor; + @XStreamAlias("rpubDate") + public String rpubDate; + @XStreamAlias("rcontent") + public String rcontent; + + public Reply() { + } + + public Reply(Parcel source) { + rauthor = source.readString(); + rpubDate = source.readString(); + rcontent = source.readString(); + } + + public String getRauthor() { + return rauthor; + } + + public void setRauthor(String rauthor) { + this.rauthor = rauthor; + } + + public String getRpubDate() { + return rpubDate; + } + + public void setRpubDate(String rpubDate) { + this.rpubDate = rpubDate; + } + + public String getRcontent() { + return rcontent; + } + + public void setRcontent(String rcontent) { + this.rcontent = rcontent; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(rauthor); + dest.writeString(rpubDate); + dest.writeString(rcontent); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator CREATOR = new Creator() { + + @Override + public Reply[] newArray(int size) { + return new Reply[size]; + } + + @Override + public Reply createFromParcel(Parcel source) { + return new Reply(source); + } + }; + + } + + @XStreamAlias("refer") + public static class Refer implements Serializable, Parcelable { + + @XStreamAlias("refertitle") + public String refertitle; + @XStreamAlias("referbody") + public String referbody; + + public Refer() { + } + + public Refer(Parcel source) { + referbody = source.readString(); + refertitle = source.readString(); + } + + public String getRefertitle() { + return refertitle; + } + + public void setRefertitle(String refertitle) { + this.refertitle = refertitle; + } + + public String getReferbody() { + return referbody; + } + + public void setReferbody(String referbody) { + this.referbody = referbody; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(referbody); + dest.writeString(refertitle); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator CREATOR = new Creator() { + + @Override + public Refer[] newArray(int size) { + return new Refer[size]; + } + + @Override + public Refer createFromParcel(Parcel source) { + return new Refer(source); + } + }; + } + + public static final Parcelable.Creator CREATOR = new Creator() { + + @Override + public Comment[] newArray(int size) { + return new Comment[size]; + } + + @Override + public Comment createFromParcel(Parcel source) { + return new Comment(source); + } + }; } diff --git a/app/src/main/java/net/oschina/app/bean/CommentList.java b/app/src/main/java/net/oschina/app/bean/CommentList.java index 0b19aa56556782a2253332b40629e1f65d5485a8..06907752bf1181730ece41a98805cb5bf3351378 100644 --- a/app/src/main/java/net/oschina/app/bean/CommentList.java +++ b/app/src/main/java/net/oschina/app/bean/CommentList.java @@ -1,18 +1,17 @@ package net.oschina.app.bean; +import com.thoughtworks.xstream.annotations.XStreamAlias; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import com.thoughtworks.xstream.annotations.XStreamAlias; - /** * 评论列表实体类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年10月14日 下午3:32:39 - * */ @SuppressWarnings("serial") @XStreamAlias("oschina") diff --git a/app/src/main/java/net/oschina/app/bean/Constants.java b/app/src/main/java/net/oschina/app/bean/Constants.java index edaf5cc4f35c0c42794998771af0e85aa68053ee..461dcc9104d11fbcd54b73fec92f80f591d0abff 100644 --- a/app/src/main/java/net/oschina/app/bean/Constants.java +++ b/app/src/main/java/net/oschina/app/bean/Constants.java @@ -1,26 +1,11 @@ package net.oschina.app.bean; /** - * 常量类 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2014年10月27日 下午12:14:42 - * + * 部分常量类 */ - public class Constants { - public static final String INTENT_ACTION_USER_CHANGE = "net.oschina.action.USER_CHANGE"; - - public static final String INTENT_ACTION_COMMENT_CHANGED = "net.oschina.action.COMMENT_CHANGED"; - - public static final String INTENT_ACTION_NOTICE = "net.oschina.action.APPWIDGET_UPDATE"; - public static final String INTENT_ACTION_LOGOUT = "net.oschina.action.LOGOUT"; - public static final String WEICHAT_APPID = "wxa8213dc827399101"; public static final String WEICHAT_SECRET = "5c716417ce72ff69d8cf0c43572c9284"; - - public static final String QQ_APPID = "100942993"; - public static final String QQ_APPKEY = "8edd3cc7ca8dcc15082d6fe75969601b"; } diff --git a/app/src/main/java/net/oschina/app/bean/Entity.java b/app/src/main/java/net/oschina/app/bean/Entity.java index 60fdf96e4cdc0b41d34925615e87a81cd3c7da7f..709a642c2428ebe0506e1799835dde8023cbd0d5 100644 --- a/app/src/main/java/net/oschina/app/bean/Entity.java +++ b/app/src/main/java/net/oschina/app/bean/Entity.java @@ -4,7 +4,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 实体类 - * + * * @author liux (http://my.oschina.net/liux) * @version 1.0 * @created 2012-3-21 @@ -18,18 +18,18 @@ public abstract class Entity extends Base { protected String cacheKey; public int getId() { - return id; + return id; } public void setId(int id) { - this.id = id; + this.id = id; } public String getCacheKey() { - return cacheKey; + return cacheKey; } public void setCacheKey(String cacheKey) { - this.cacheKey = cacheKey; + this.cacheKey = cacheKey; } } diff --git a/app/src/main/java/net/oschina/app/bean/Event.java b/app/src/main/java/net/oschina/app/bean/Event.java index 25c50e8a348e0d3b031cb6d4748f479f38640326..1f6f306c4a73c3cccfd847cea6230df9fc33401e 100644 --- a/app/src/main/java/net/oschina/app/bean/Event.java +++ b/app/src/main/java/net/oschina/app/bean/Event.java @@ -4,183 +4,182 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 活动实体类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @version 创建时间:2014年12月12日 下午3:18:08 - * */ @SuppressWarnings("serial") @XStreamAlias("event") public class Event extends Entity { - - public final static int EVNET_STATUS_APPLYING = 0x02; - public final static int EVNET_STATUS_END = 0x01; - - public final static int APPLYSTATUS_CHECKING = 0x00;// 审核中 - public final static int APPLYSTATUS_CHECKED = 0x01;// 已经确认 - public final static int APPLYSTATUS_ATTEND = 0x02;// 已经出席 - public final static int APPLYSTATUS_CANCLE = 0x03;// 已取消 - public final static int APPLYSTATUS_REJECT = 0X04;// 已拒绝 - - @XStreamAlias("cover") - private String cover; - - @XStreamAlias("title") - private String title; - - @XStreamAlias("url") - private String url; - - @XStreamAlias("createTime") - private String createTime; - - @XStreamAlias("startTime") - private String startTime; - - @XStreamAlias("endTime") - private String endTime; - - @XStreamAlias("spot") - private String spot; - - @XStreamAlias("actor_count") - private int actor_count; - - @XStreamAlias("location") - private String location; - - @XStreamAlias("city") - private String city; - - @XStreamAlias("status") - private int status; - - @XStreamAlias("applyStatus") - private int applyStatus; - - @XStreamAlias("category") - private int category;// 活动类型 1源创会 2技术交流 3其他 4站外活动 - - @XStreamAlias("remark") - private EventRemark eventRemark; - - public int getCategory() { - return category; - } - - public void setCategory(int category) { - this.category = category; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getCover() { - return cover; - } - - public void setCover(String cover) { - this.cover = cover; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getCreateTime() { - return createTime; - } - - public void setCreateTime(String createTime) { - this.createTime = createTime; - } - - public String getStartTime() { - return startTime; - } - - public void setStartTime(String startTime) { - this.startTime = startTime; - } - - public String getEndTime() { - return endTime; - } - - public void setEndTime(String endTime) { - this.endTime = endTime; - } - - public String getSpot() { - return spot; - } - - public void setSpot(String spot) { - this.spot = spot; - } - - public int getActor_count() { - return actor_count; - } - - public void setActor_count(int actor_count) { - this.actor_count = actor_count; - } - - public String getLocation() { - return location; - } - - public void setLocation(String location) { - this.location = location; - } - public int getStatus() { - return status; - } + public final static int EVNET_STATUS_APPLYING = 0x02; + public final static int EVNET_STATUS_END = 0x01; - public void setStatus(int status) { - this.status = status; - } + public final static int APPLYSTATUS_CHECKING = 0x00;// 审核中 + public final static int APPLYSTATUS_CHECKED = 0x01;// 已经确认 + public final static int APPLYSTATUS_ATTEND = 0x02;// 已经出席 + public final static int APPLYSTATUS_CANCLE = 0x03;// 已取消 + public final static int APPLYSTATUS_REJECT = 0X04;// 已拒绝 - public int getApplyStatus() { - return applyStatus; - } + @XStreamAlias("cover") + private String cover; - public void setApplyStatus(int applyStatus) { - this.applyStatus = applyStatus; - } + @XStreamAlias("title") + private String title; - public EventRemark getEventRemark() { - return eventRemark; - } + @XStreamAlias("url") + private String url; - public void setEventRemark(EventRemark eventRemark) { - this.eventRemark = eventRemark; - } + @XStreamAlias("createTime") + private String createTime; + + @XStreamAlias("startTime") + private String startTime; + + @XStreamAlias("endTime") + private String endTime; + + @XStreamAlias("spot") + private String spot; + + @XStreamAlias("actor_count") + private int actor_count; + + @XStreamAlias("location") + private String location; + + @XStreamAlias("city") + private String city; + + @XStreamAlias("status") + private int status; + + @XStreamAlias("applyStatus") + private int applyStatus; + + @XStreamAlias("category") + private int category;// 活动类型 1源创会 2技术交流 3其他 4站外活动 + + @XStreamAlias("remark") + private EventRemark eventRemark; + + public int getCategory() { + return category; + } + + public void setCategory(int category) { + this.category = category; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getCover() { + return cover; + } + + public void setCover(String cover) { + this.cover = cover; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getCreateTime() { + return createTime; + } + + public void setCreateTime(String createTime) { + this.createTime = createTime; + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + + public String getSpot() { + return spot; + } + + public void setSpot(String spot) { + this.spot = spot; + } + + public int getActor_count() { + return actor_count; + } + + public void setActor_count(int actor_count) { + this.actor_count = actor_count; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public int getApplyStatus() { + return applyStatus; + } + + public void setApplyStatus(int applyStatus) { + this.applyStatus = applyStatus; + } + + public EventRemark getEventRemark() { + return eventRemark; + } + + public void setEventRemark(EventRemark eventRemark) { + this.eventRemark = eventRemark; + } } diff --git a/app/src/main/java/net/oschina/app/bean/EventAppliesList.java b/app/src/main/java/net/oschina/app/bean/EventAppliesList.java index 9ac5dfb07e6f4d1af7bcad55b6a0730c519bf26c..14c8cd3e81d2f9e88c5843c087363ffbab1b8ab9 100644 --- a/app/src/main/java/net/oschina/app/bean/EventAppliesList.java +++ b/app/src/main/java/net/oschina/app/bean/EventAppliesList.java @@ -1,33 +1,32 @@ package net.oschina.app.bean; +import com.thoughtworks.xstream.annotations.XStreamAlias; + import java.util.ArrayList; import java.util.List; -import com.thoughtworks.xstream.annotations.XStreamAlias; - /** * 活动参与者列表实体类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年12月12日 下午8:06:30 - * */ @SuppressWarnings("serial") @XStreamAlias("oschina") public class EventAppliesList extends Entity implements ListEntity { - public final static int TYPE_FANS = 0x00; - public final static int TYPE_FOLLOWER = 0x01; - - @XStreamAlias("applies") - private List list = new ArrayList(); + public final static int TYPE_FANS = 0x00; + public final static int TYPE_FOLLOWER = 0x01; + + @XStreamAlias("applies") + private List list = new ArrayList(); - @Override - public List getList() { - return list; - } + @Override + public List getList() { + return list; + } - public void setList(List list) { - this.list = list; - } + public void setList(List list) { + this.list = list; + } } diff --git a/app/src/main/java/net/oschina/app/bean/EventApplyData.java b/app/src/main/java/net/oschina/app/bean/EventApplyData.java index 89a6a5d65384f20f1723ff2d7ae27612679074ea..e03b53178cfc40557a12eca1ebaa7837bede382e 100644 --- a/app/src/main/java/net/oschina/app/bean/EventApplyData.java +++ b/app/src/main/java/net/oschina/app/bean/EventApplyData.java @@ -2,91 +2,90 @@ package net.oschina.app.bean; /** * 活动报名实体类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年12月17日 下午3:13:07 - * */ @SuppressWarnings("serial") public class EventApplyData extends Entity { - - private int event;// 活动的id - - private int user;// 用户的id - - private String name;// 名字 - - private String gender;// 性别 - - private String mobile;// 电话 - - private String company;// 单位名称 - - private String job;// 职业名称 - - private String remark;// 备注 - - public int getEvent() { - return event; - } - - public void setEvent(int event) { - this.event = event; - } - - public int getUser() { - return user; - } - - public void setUser(int user) { - this.user = user; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getGender() { - return gender; - } - - public void setGender(String gender) { - this.gender = gender; - } - - public String getPhone() { - return mobile; - } - - public void setPhone(String phone) { - this.mobile = phone; - } - - public String getCompany() { - return company; - } - - public void setCompany(String company) { - this.company = company; - } - - public String getJob() { - return job; - } - - public void setJob(String job) { - this.job = job; - } - - public String getRemark() { - return remark; - } - - public void setRemark(String remark) { - this.remark = remark; - } + + private int event;// 活动的id + + private int user;// 用户的id + + private String name;// 名字 + + private String gender;// 性别 + + private String mobile;// 电话 + + private String company;// 单位名称 + + private String job;// 职业名称 + + private String remark;// 备注 + + public int getEvent() { + return event; + } + + public void setEvent(int event) { + this.event = event; + } + + public int getUser() { + return user; + } + + public void setUser(int user) { + this.user = user; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getGender() { + return gender; + } + + public void setGender(String gender) { + this.gender = gender; + } + + public String getPhone() { + return mobile; + } + + public void setPhone(String phone) { + this.mobile = phone; + } + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + + public String getJob() { + return job; + } + + public void setJob(String job) { + this.job = job; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } } diff --git a/app/src/main/java/net/oschina/app/bean/EventList.java b/app/src/main/java/net/oschina/app/bean/EventList.java index ad2f80a5fb4518b79848169015c4035c034b890f..6d8663929499c619e068f9be4ed4186e77d48f1a 100644 --- a/app/src/main/java/net/oschina/app/bean/EventList.java +++ b/app/src/main/java/net/oschina/app/bean/EventList.java @@ -1,38 +1,37 @@ package net.oschina.app.bean; +import com.thoughtworks.xstream.annotations.XStreamAlias; + import java.util.ArrayList; import java.util.List; -import com.thoughtworks.xstream.annotations.XStreamAlias; - /** * 活动实体类列表 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年12月10日 下午2:28:54 - * */ @SuppressWarnings("serial") @XStreamAlias("oschina") public class EventList extends Entity implements ListEntity { - public final static int EVENT_LIST_TYPE_NEW_EVENT = 0X00;// 近期活动 + public final static int EVENT_LIST_TYPE_NEW_EVENT = 0X00;// 近期活动 - public final static int EVENT_LIST_TYPE_MY_EVENT = 0X01;// 我的活动 + public final static int EVENT_LIST_TYPE_MY_EVENT = 0X01;// 我的活动 - @XStreamAlias("events") - private List friendlist = new ArrayList(); + @XStreamAlias("events") + private List friendlist = new ArrayList(); - public List getFriendlist() { - return friendlist; - } + public List getFriendlist() { + return friendlist; + } - public void setFriendlist(List resultlist) { - this.friendlist = resultlist; - } + public void setFriendlist(List resultlist) { + this.friendlist = resultlist; + } - @Override - public List getList() { - return friendlist; - } + @Override + public List getList() { + return friendlist; + } } diff --git a/app/src/main/java/net/oschina/app/bean/Favorite.java b/app/src/main/java/net/oschina/app/bean/Favorite.java index d229ffdb478acfdce9b74c49dae37a96fbe3cfb4..04b514e5c1134d6835f45bddc7e09c022865fde0 100644 --- a/app/src/main/java/net/oschina/app/bean/Favorite.java +++ b/app/src/main/java/net/oschina/app/bean/Favorite.java @@ -4,51 +4,59 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 收藏实体类 - * @author hww * + * @author hww */ @SuppressWarnings("serial") @XStreamAlias("favorite") public class Favorite extends Entity { - - public static final int CATALOG_ALL = 0; - public static final int CATALOG_SOFTWARE = 1; - public static final int CATALOG_TOPIC = 2; - public static final int CATALOG_BLOGS = 3; - public static final int CATALOG_NEWS = 4; - public static final int CATALOG_CODE = 5; - - @XStreamAlias("objid") - public int id; - @XStreamAlias("type") - public int type; - @XStreamAlias("title") - public String title; - @XStreamAlias("url") - public String url; - public int getId() { - return id; - } - public void setId(int id) { - this.id = id; - } - public int getType() { - return type; - } - public void setType(int type) { - this.type = type; - } - public String getTitle() { - return title; - } - public void setTitle(String title) { - this.title = title; - } - public String getUrl() { - return url; - } - public void setUrl(String url) { - this.url = url; - } - + + public static final int CATALOG_ALL = 0; + public static final int CATALOG_SOFTWARE = 1; + public static final int CATALOG_TOPIC = 2; + public static final int CATALOG_BLOGS = 3; + public static final int CATALOG_NEWS = 4; + public static final int CATALOG_CODE = 5; + + @XStreamAlias("objid") + public int id; + @XStreamAlias("type") + public int type; + @XStreamAlias("title") + public String title; + @XStreamAlias("url") + public String url; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + } diff --git a/app/src/main/java/net/oschina/app/bean/FavoriteList.java b/app/src/main/java/net/oschina/app/bean/FavoriteList.java deleted file mode 100644 index 513eb64a430dd331f2a57b4e2b6c90b2de4f0101..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/FavoriteList.java +++ /dev/null @@ -1,50 +0,0 @@ -package net.oschina.app.bean; - -import java.util.ArrayList; -import java.util.List; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 收藏实体类 - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年10月14日 下午2:27:39 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class FavoriteList extends Entity implements ListEntity { - - public final static int TYPE_ALL = 0x00; - public final static int TYPE_SOFTWARE = 0x01; - public final static int TYPE_POST = 0x02; - public final static int TYPE_BLOG = 0x03; - public final static int TYPE_NEWS = 0x04; - public final static int TYPE_CODE = 0x05; - - @XStreamAlias("pagesize") - private int pageSize; - @XStreamAlias("favorites") - private List favoritelist = new ArrayList(); - - public int getPageSize() { - return pageSize; - } - - public void setPageSize(int pagesize) { - this.pageSize = pagesize; - } - - public List getFavoritelist() { - return favoritelist; - } - - public void setFavoritelist(List favoritelist) { - this.favoritelist = favoritelist; - } - - @Override - public List getList() { - return favoritelist; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/FindUserList.java b/app/src/main/java/net/oschina/app/bean/FindUserList.java deleted file mode 100644 index 165884e018ef145701c596b562db6c377da34b60..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/FindUserList.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.oschina.app.bean; - -import java.util.ArrayList; -import java.util.List; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 好友实体类 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年11月6日 上午11:17:36 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class FindUserList extends Entity implements ListEntity { - - public final static int TYPE_FANS = 0x00; - public final static int TYPE_FOLLOWER = 0x01; - - @XStreamAlias("users") - private List friendlist = new ArrayList(); - - public List getFriendlist() { - return friendlist; - } - - public void setFriendlist(List resultlist) { - this.friendlist = resultlist; - } - - @Override - public List getList() { - return friendlist; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/Friend.java b/app/src/main/java/net/oschina/app/bean/Friend.java index eeaa23adbfeea11100c4ac6534f700c3d2114b30..177ee0cfaca60c97024d9cbd4095744c11f2eb04 100644 --- a/app/src/main/java/net/oschina/app/bean/Friend.java +++ b/app/src/main/java/net/oschina/app/bean/Friend.java @@ -4,30 +4,29 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 好友实体类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年11月6日 上午11:37:31 - * */ @SuppressWarnings("serial") @XStreamAlias("friend") public class Friend extends Entity { - + @XStreamAlias("userid") private int userid; - + @XStreamAlias("name") private String name; - + @XStreamAlias("from") private String from; - + @XStreamAlias("portrait") private String portrait; - + @XStreamAlias("expertise") private String expertise; - + @XStreamAlias("gender") private int gender; @@ -48,14 +47,14 @@ public class Friend extends Entity { } public String getFrom() { - return from; - } + return from; + } - public void setFrom(String from) { - this.from = from; - } + public void setFrom(String from) { + this.from = from; + } - public String getPortrait() { + public String getPortrait() { return portrait; } diff --git a/app/src/main/java/net/oschina/app/bean/FriendsList.java b/app/src/main/java/net/oschina/app/bean/FriendsList.java index 9c73190f12d51382d58166bf1bf54dec000699e4..de5131a56c7ae463b22481221e9a597f184039ea 100644 --- a/app/src/main/java/net/oschina/app/bean/FriendsList.java +++ b/app/src/main/java/net/oschina/app/bean/FriendsList.java @@ -1,16 +1,15 @@ package net.oschina.app.bean; +import com.thoughtworks.xstream.annotations.XStreamAlias; + import java.util.ArrayList; import java.util.List; -import com.thoughtworks.xstream.annotations.XStreamAlias; - /** * 好友实体类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年11月6日 上午11:17:36 - * */ @SuppressWarnings("serial") @XStreamAlias("oschina") @@ -23,15 +22,15 @@ public class FriendsList extends Entity implements ListEntity { private List friendlist = new ArrayList(); public List getFriendlist() { - return friendlist; + return friendlist; } public void setFriendlist(List resultlist) { - this.friendlist = resultlist; + this.friendlist = resultlist; } @Override public List getList() { - return friendlist; + return friendlist; } } diff --git a/app/src/main/java/net/oschina/app/bean/LoginUserBean.java b/app/src/main/java/net/oschina/app/bean/LoginUserBean.java deleted file mode 100644 index a6d7ac9a4b4252b296f2713a60e3491ce6cb4a38..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/LoginUserBean.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.oschina.app.bean; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2014年9月27日 下午2:45:57 - * - */ - -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class LoginUserBean extends Entity { - - @XStreamAlias("result") - private Result result; - - @XStreamAlias("user") - private User user; - - public Result getResult() { - return result; - } - - public void setResult(Result result) { - this.result = result; - } - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } - -} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/bean/MessageDetail.java b/app/src/main/java/net/oschina/app/bean/MessageDetail.java index 222440b4dfeb5587085c61cdeaa9064704d60023..939a6868705e34ba53b5eaeaa009820d7258442e 100644 --- a/app/src/main/java/net/oschina/app/bean/MessageDetail.java +++ b/app/src/main/java/net/oschina/app/bean/MessageDetail.java @@ -4,12 +4,13 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 聊天详细信息实体类 + * * @author 铂金小鸟(http://my.oschina.net/fants) * @Created 2015年9月16日 上午4:20:01 */ @SuppressWarnings("serial") @XStreamAlias("message") -public class MessageDetail extends Entity { +public class MessageDetail extends Entity { //是否显示时间 private boolean showDate; @@ -37,8 +38,8 @@ public class MessageDetail extends Entity { @XStreamAlias("pubDate") private String pubDate; - public enum MessageStatus{ - NORMAL,SENDING,ERROR + public enum MessageStatus { + NORMAL, SENDING, ERROR } public String getPortrait() { diff --git a/app/src/main/java/net/oschina/app/bean/MessageDetailList.java b/app/src/main/java/net/oschina/app/bean/MessageDetailList.java deleted file mode 100644 index 7684a8dcbbd88ad051b11955f14f7f1c651a917b..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/MessageDetailList.java +++ /dev/null @@ -1,43 +0,0 @@ -package net.oschina.app.bean; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -import java.util.ArrayList; -import java.util.List; - -/** - * 聊天详细信息实体类 - * @author 铂金小鸟(http://my.oschina.net/fants) - * @Created 2015年9月16日 上午4:20:01 - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class MessageDetailList extends Entity implements ListEntity { - - @XStreamAlias("allCount") - private int allCount; - - @XStreamAlias("pagesize") - private int pageSize; - - @XStreamAlias("messages") - private List messagelist = new ArrayList(); - - public int getPageSize() { - return pageSize; - } - - public int getMessageCount() { - return allCount; - } - - public List getMessagelist() { - return messagelist; - } - - @Override - public List getList() { - return messagelist; - } - -} diff --git a/app/src/main/java/net/oschina/app/bean/MessageList.java b/app/src/main/java/net/oschina/app/bean/MessageList.java deleted file mode 100644 index 63cb67eb4fddc6f98cf61ac0c0d11448cde479c4..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/MessageList.java +++ /dev/null @@ -1,43 +0,0 @@ -package net.oschina.app.bean; - -import java.util.ArrayList; -import java.util.List; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 留言实体类列表 - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年10月22日 下午4:38:49 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class MessageList extends Entity implements ListEntity { - - @XStreamAlias("pagesize") - private int pageSize; - - @XStreamAlias("messageCount") - private int messageCount; - - @XStreamAlias("messages") - private List messagelist = new ArrayList(); - - public int getPageSize() { - return pageSize; - } - - public int getMessageCount() { - return messageCount; - } - - public List getMessagelist() { - return messagelist; - } - - @Override - public List getList() { - return messagelist; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/Messages.java b/app/src/main/java/net/oschina/app/bean/Messages.java index e004f82dc910fc1460c5635eb5ce177fb6fb53b5..ab0b92bf8454a8af03fd7c1cc30517b0a5308380 100644 --- a/app/src/main/java/net/oschina/app/bean/Messages.java +++ b/app/src/main/java/net/oschina/app/bean/Messages.java @@ -4,115 +4,115 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 留言实体类 + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年10月22日 下午4:43:01 - * */ @SuppressWarnings("serial") @XStreamAlias("message") public class Messages extends Entity { - public final static int CLIENT_MOBILE = 2; - public final static int CLIENT_ANDROID = 3; - public final static int CLIENT_IPHONE = 4; - public final static int CLIENT_WINDOWS_PHONE = 5; + public final static int CLIENT_MOBILE = 2; + public final static int CLIENT_ANDROID = 3; + public final static int CLIENT_IPHONE = 4; + public final static int CLIENT_WINDOWS_PHONE = 5; - @XStreamAlias("portrait") - private String portrait; + @XStreamAlias("portrait") + private String portrait; - @XStreamAlias("friendid") - private int friendId; + @XStreamAlias("friendid") + private int friendId; - @XStreamAlias("friendname") - private String friendName; + @XStreamAlias("friendname") + private String friendName; - @XStreamAlias("sender") - private String sender; + @XStreamAlias("sender") + private String sender; - @XStreamAlias("senderid") - private int senderId; + @XStreamAlias("senderid") + private int senderId; - @XStreamAlias("content") - private String content; + @XStreamAlias("content") + private String content; - @XStreamAlias("messageCount") - private int messageCount; + @XStreamAlias("messageCount") + private int messageCount; - @XStreamAlias("pubDate") - private String pubDate; + @XStreamAlias("pubDate") + private String pubDate; - @XStreamAlias("appClient") - private int appClient; + @XStreamAlias("appClient") + private int appClient; - public String getPortrait() { - return portrait; - } + public String getPortrait() { + return portrait; + } - public void setPortrait(String portrait) { - this.portrait = portrait; - } + public void setPortrait(String portrait) { + this.portrait = portrait; + } - public int getFriendId() { - return friendId; - } + public int getFriendId() { + return friendId; + } - public void setFriendId(int friendId) { - this.friendId = friendId; - } + public void setFriendId(int friendId) { + this.friendId = friendId; + } - public String getFriendName() { - return friendName; - } + public String getFriendName() { + return friendName; + } - public void setFriendName(String friendName) { - this.friendName = friendName; - } + public void setFriendName(String friendName) { + this.friendName = friendName; + } - public String getSender() { - return sender; - } + public String getSender() { + return sender; + } - public void setSender(String sender) { - this.sender = sender; - } + public void setSender(String sender) { + this.sender = sender; + } - public int getSenderId() { - return senderId; - } + public int getSenderId() { + return senderId; + } - public void setSenderId(int senderId) { - this.senderId = senderId; - } + public void setSenderId(int senderId) { + this.senderId = senderId; + } - public String getContent() { - return content; - } + public String getContent() { + return content; + } - public void setContent(String content) { - this.content = content; - } + public void setContent(String content) { + this.content = content; + } - public int getMessageCount() { - return messageCount; - } + public int getMessageCount() { + return messageCount; + } - public void setMessageCount(int messageCount) { - this.messageCount = messageCount; - } + public void setMessageCount(int messageCount) { + this.messageCount = messageCount; + } - public String getPubDate() { - return pubDate; - } + public String getPubDate() { + return pubDate; + } - public void setPubDate(String pubDate) { - this.pubDate = pubDate; - } + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } - public int getAppClient() { - return appClient; - } + public int getAppClient() { + return appClient; + } - public void setAppClient(int appClient) { - this.appClient = appClient; - } + public void setAppClient(int appClient) { + this.appClient = appClient; + } } diff --git a/app/src/main/java/net/oschina/app/bean/MyInformation.java b/app/src/main/java/net/oschina/app/bean/MyInformation.java deleted file mode 100644 index 91a2bf214ead1ec8eedd2b12918ec9f5ec21091d..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/MyInformation.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.oschina.app.bean; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 我的资料实体类 - * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2014年10月30日 下午4:08:30 - * - */ - -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class MyInformation extends Base { - - @XStreamAlias("user") - private User user; - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/News.java b/app/src/main/java/net/oschina/app/bean/News.java index fb1ab9a00181c2bd2070bb3b406d3f5a146ec6e9..a77f6e06ba62ae8669a3b3beae4af9b15ebe1966 100644 --- a/app/src/main/java/net/oschina/app/bean/News.java +++ b/app/src/main/java/net/oschina/app/bean/News.java @@ -1,225 +1,235 @@ package net.oschina.app.bean; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; +import com.thoughtworks.xstream.annotations.XStreamAlias; import net.oschina.app.util.StringUtils; -import com.thoughtworks.xstream.annotations.XStreamAlias; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; /** * 新闻、软件、帖子、博客实体类 + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年9月28日 上午10:16:59 - * */ @SuppressWarnings("serial") @XStreamAlias("news") public class News extends Entity { - - public final static int NEWSTYPE_NEWS = 0x00;//0 新闻 - public final static int NEWSTYPE_SOFTWARE = 0x01;//1 软件 - public final static int NEWSTYPE_POST = 0x02;//2 帖子 - public final static int NEWSTYPE_BLOG = 0x03;//3 博客 - - @XStreamAlias("title") - private String title; - - @XStreamAlias("url") - private String url; - - @XStreamAlias("body") - private String body; - - @XStreamAlias("author") - private String author; - - @XStreamAlias("authorid") - private int authorId; - - @XStreamAlias("commentcount") - private int commentCount; - - @XStreamAlias("pubdate") - private String pubDate; - - @XStreamAlias("softwarelink") - private String softwareLink; - - @XStreamAlias("softwarename") - private String softwareName; - - @XStreamAlias("favorite") - private int favorite; - - @XStreamAlias("newstype") - private NewsType newsType; - - @XStreamAlias("relativies") - private List relatives = new ArrayList(); - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getBody() { - return body; - } - - public void setBody(String body) { - this.body = body; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } - - public int getAuthorId() { - return authorId; - } - - public void setAuthorId(String authorId) { - this.authorId = StringUtils.toInt(authorId, 0); - } - - public int getCommentCount() { - return commentCount; - } - - public void setCommentCount(int commentCount) { - this.commentCount = commentCount; - } - - public String getPubDate() { - return pubDate; - } - - public void setPubDate(String pubDate) { - this.pubDate = pubDate; - } - - public String getSoftwareLink() { - return softwareLink; - } - - public void setSoftwareLink(String softwareLink) { - this.softwareLink = softwareLink; - } - - public String getSoftwareName() { - return softwareName; - } - - public void setSoftwareName(String softwareName) { - this.softwareName = softwareName; - } - - public int getFavorite() { - return favorite; - } - - public void setFavorite(int favorite) { - this.favorite = favorite; - } - - public NewsType getNewType() { - return newsType; - } - - public void setNewType(NewsType newType) { - this.newsType = newType; - } - - public List getRelatives() { - return relatives; - } - - public void setRelatives(List relatives) { - this.relatives = relatives; - } - - @XStreamAlias("newstype") - public class NewsType implements Serializable{ - @XStreamAlias("type") - private int type; - @XStreamAlias("attachment") - private String attachment; - @XStreamAlias("authoruid2") - private int authoruid2; - @XStreamAlias("eventurl") - private String eventUrl; - - public String getEventUrl() { - return eventUrl; - } - public void setEventUrl(String eventUrl) { - this.eventUrl = eventUrl; - } - public int getType() { - return type; - } - public void setType(int type) { - this.type = type; - } - public String getAttachment() { - return attachment; - } - public void setAttachment(String attachment) { - this.attachment = attachment; - } - public int getAuthoruid2() { - return authoruid2; - } - public void setAuthoruid2(int authoruid2) { - this.authoruid2 = authoruid2; - } - } - - @XStreamAlias("relative") - public class Relative implements Serializable{ - - @XStreamAlias("rtitle") - public String title; - - @XStreamAlias("rurl") - public String url; - - public String getTitle() { - return title; - } - public void setTitle(String title) { - this.title = title; - } - public String getUrl() { - return url; - } - public void setUrl(String url) { - this.url = url; - } - } + + public final static int NEWSTYPE_NEWS = 0x00;//0 新闻 + public final static int NEWSTYPE_SOFTWARE = 0x01;//1 软件 + public final static int NEWSTYPE_POST = 0x02;//2 帖子 + public final static int NEWSTYPE_BLOG = 0x03;//3 博客 + + @XStreamAlias("title") + private String title; + + @XStreamAlias("url") + private String url; + + @XStreamAlias("body") + private String body; + + @XStreamAlias("author") + private String author; + + @XStreamAlias("authorid") + private int authorId; + + @XStreamAlias("commentcount") + private int commentCount; + + @XStreamAlias("pubdate") + private String pubDate; + + @XStreamAlias("softwarelink") + private String softwareLink; + + @XStreamAlias("softwarename") + private String softwareName; + + @XStreamAlias("favorite") + private int favorite; + + @XStreamAlias("newstype") + private NewsType newsType; + + @XStreamAlias("relativies") + private List relatives = new ArrayList(); + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public int getAuthorId() { + return authorId; + } + + public void setAuthorId(String authorId) { + this.authorId = StringUtils.toInt(authorId, 0); + } + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public String getSoftwareLink() { + return softwareLink; + } + + public void setSoftwareLink(String softwareLink) { + this.softwareLink = softwareLink; + } + + public String getSoftwareName() { + return softwareName; + } + + public void setSoftwareName(String softwareName) { + this.softwareName = softwareName; + } + + public int getFavorite() { + return favorite; + } + + public void setFavorite(int favorite) { + this.favorite = favorite; + } + + public NewsType getNewType() { + return newsType; + } + + public void setNewType(NewsType newType) { + this.newsType = newType; + } + + public List getRelatives() { + return relatives; + } + + public void setRelatives(List relatives) { + this.relatives = relatives; + } + + @XStreamAlias("newstype") + public class NewsType implements Serializable { + @XStreamAlias("type") + private int type; + @XStreamAlias("attachment") + private String attachment; + @XStreamAlias("authoruid2") + private int authoruid2; + @XStreamAlias("eventurl") + private String eventUrl; + + public String getEventUrl() { + return eventUrl; + } + + public void setEventUrl(String eventUrl) { + this.eventUrl = eventUrl; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getAttachment() { + return attachment; + } + + public void setAttachment(String attachment) { + this.attachment = attachment; + } + + public int getAuthoruid2() { + return authoruid2; + } + + public void setAuthoruid2(int authoruid2) { + this.authoruid2 = authoruid2; + } + } + + @XStreamAlias("relative") + public class Relative implements Serializable { + + @XStreamAlias("rtitle") + public String title; + + @XStreamAlias("rurl") + public String url; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + } } diff --git a/app/src/main/java/net/oschina/app/bean/NewsDetail.java b/app/src/main/java/net/oschina/app/bean/NewsDetail.java deleted file mode 100644 index ce68418a35935ed38ee4ed3b948141ebaf105b83..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/NewsDetail.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.oschina.app.bean; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 资讯详情 - * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2014年10月11日 下午3:28:33 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class NewsDetail extends Entity { - - @XStreamAlias("news") - private News news; - - public News getNews() { - return news; - } - - public void setNews(News news) { - this.news = news; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/NewsList.java b/app/src/main/java/net/oschina/app/bean/NewsList.java deleted file mode 100644 index c353462a01a375f5b9bd762741659f9ea7e32a40..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/NewsList.java +++ /dev/null @@ -1,71 +0,0 @@ -package net.oschina.app.bean; - -import java.util.ArrayList; -import java.util.List; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 新闻列表实体类 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年9月27日 下午5:55:58 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class NewsList extends Entity implements ListEntity { - - public final static String PREF_READED_NEWS_LIST = "readed_news_list.pref"; - - public final static int CATALOG_ALL = 1; - public final static int CATALOG_INTEGRATION = 2; - public final static int CATALOG_SOFTWARE = 3; - - public final static int CATALOG_WEEK = 4; - public final static int CATALOG_MONTH = 5; - - @XStreamAlias("catalog") - private int catalog; - - @XStreamAlias("pagesize") - private int pageSize; - - @XStreamAlias("newscount") - private int newsCount; - - @XStreamAlias("newslist") - private List list = new ArrayList(); - - public int getCatalog() { - return catalog; - } - - public void setCatalog(int catalog) { - this.catalog = catalog; - } - - public int getPageSize() { - return pageSize; - } - - public void setPageSize(int pageSize) { - this.pageSize = pageSize; - } - - public int getNewsCount() { - return newsCount; - } - - public void setNewsCount(int newsCount) { - this.newsCount = newsCount; - } - - public List getList() { - return list; - } - - public void setList(List list) { - this.list = list; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/NotebookData.java b/app/src/main/java/net/oschina/app/bean/NotebookData.java index 1e429ece94fdfa671c7cc4355f1e5eb6d09a679f..6a9ae6047654bd95028650b199791693b1d7a120 100644 --- a/app/src/main/java/net/oschina/app/bean/NotebookData.java +++ b/app/src/main/java/net/oschina/app/bean/NotebookData.java @@ -1,14 +1,13 @@ package net.oschina.app.bean; -import java.io.Serializable; - import com.thoughtworks.xstream.annotations.XStreamAlias; +import java.io.Serializable; + /** * 便签数据bean(有重载equals()方法) - * + * * @author kymjs (https://github.com/kymjs) - * */ @XStreamAlias("sticky") public class NotebookData extends Entity implements Serializable, @@ -131,24 +130,24 @@ public class NotebookData extends Entity implements Serializable, public void setColor(int color) { switch (color) { - case 0: - colorText = "green"; - break; - case 1: - colorText = "yellow"; - break; - case 2: - colorText = "red"; - break; - case 3: - colorText = "blue"; - break; - case 4: - colorText = "purple"; - break; - default: - this.color = color; - break; + case 0: + colorText = "green"; + break; + case 1: + colorText = "yellow"; + break; + case 2: + colorText = "red"; + break; + case 3: + colorText = "blue"; + break; + case 4: + colorText = "purple"; + break; + default: + this.color = color; + break; } } diff --git a/app/src/main/java/net/oschina/app/bean/NotebookDataList.java b/app/src/main/java/net/oschina/app/bean/NotebookDataList.java index 5a9c5a790b5f8534146b98831f9570b18b3cb8da..9ce01328828c9aadca061db764c93d813203e266 100644 --- a/app/src/main/java/net/oschina/app/bean/NotebookDataList.java +++ b/app/src/main/java/net/oschina/app/bean/NotebookDataList.java @@ -1,10 +1,10 @@ package net.oschina.app.bean; +import com.thoughtworks.xstream.annotations.XStreamAlias; + import java.util.ArrayList; import java.util.List; -import com.thoughtworks.xstream.annotations.XStreamAlias; - @XStreamAlias("oschina") public class NotebookDataList extends Entity implements ListEntity { diff --git a/app/src/main/java/net/oschina/app/bean/Notice.java b/app/src/main/java/net/oschina/app/bean/Notice.java index 8d5717bbc4489c3024f050100677077af02e3665..6607a03773efd84be217c100795d2cf62f7701c3 100644 --- a/app/src/main/java/net/oschina/app/bean/Notice.java +++ b/app/src/main/java/net/oschina/app/bean/Notice.java @@ -1,12 +1,12 @@ package net.oschina.app.bean; -import java.io.Serializable; - import com.thoughtworks.xstream.annotations.XStreamAlias; +import java.io.Serializable; + /** * 通知信息实体类 - * + * * @author liux (http://my.oschina.net/liux) * @version 1.0 * @created 2012-3-21 @@ -40,35 +40,35 @@ public class Notice implements Serializable { private int newLikeCount; public int getAtmeCount() { - return atmeCount; + return atmeCount; } public void setAtmeCount(int atmeCount) { - this.atmeCount = atmeCount; + this.atmeCount = atmeCount; } public int getMsgCount() { - return msgCount; + return msgCount; } public void setMsgCount(int msgCount) { - this.msgCount = msgCount; + this.msgCount = msgCount; } public int getReviewCount() { - return reviewCount; + return reviewCount; } public void setReviewCount(int reviewCount) { - this.reviewCount = reviewCount; + this.reviewCount = reviewCount; } public int getNewFansCount() { - return newFansCount; + return newFansCount; } public void setNewFansCount(int newFansCount) { - this.newFansCount = newFansCount; + this.newFansCount = newFansCount; } public int getNewLikeCount() { diff --git a/app/src/main/java/net/oschina/app/bean/NoticeDetail.java b/app/src/main/java/net/oschina/app/bean/NoticeDetail.java deleted file mode 100644 index 776fe5fb7128e90447a7e54d5f9956443c84f14f..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/NoticeDetail.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.oschina.app.bean; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 通知实体类 - * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2014年10月27日 下午2:28:42 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class NoticeDetail extends Base { - - @XStreamAlias("result") - private Result result; - - public Result getResult() { - return result; - } - - public void setResult(Result result) { - this.result = result; - } - - -} diff --git a/app/src/main/java/net/oschina/app/bean/OpenIdCatalog.java b/app/src/main/java/net/oschina/app/bean/OpenIdCatalog.java deleted file mode 100644 index 7d6efe9cb4cb32b2e269c7180ee46dda6bedd8de..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/OpenIdCatalog.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.oschina.app.bean; - -/** - * Created by zhangdeyi on 15/7/22. - */ -public class OpenIdCatalog { - - public static final String QQ = "qq"; - public static final String WEIBO = "weibo"; - public static final String WECHAT = "wechat"; - public static final String GITHUB = "github"; -} diff --git a/app/src/main/java/net/oschina/app/bean/Post.java b/app/src/main/java/net/oschina/app/bean/Post.java index b6441f74aa2cc1e892bb23e96524139d674434d5..c6a193956a2807a33d8c7ac2e8a0b582248ccd39 100644 --- a/app/src/main/java/net/oschina/app/bean/Post.java +++ b/app/src/main/java/net/oschina/app/bean/Post.java @@ -1,229 +1,228 @@ package net.oschina.app.bean; -import java.io.Serializable; -import java.util.List; - import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamImplicit; +import java.io.Serializable; +import java.util.List; + /** * 帖子实体类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年10月9日 下午6:02:47 - * */ @SuppressWarnings("serial") @XStreamAlias("post") public class Post extends Entity { - public final static int CATALOG_ASK = 1; - public final static int CATALOG_SHARE = 2; - public final static int CATALOG_OTHER = 3; - public final static int CATALOG_JOB = 4; - public final static int CATALOG_SITE = 5; + public final static int CATALOG_ASK = 1; + public final static int CATALOG_SHARE = 2; + public final static int CATALOG_OTHER = 3; + public final static int CATALOG_JOB = 4; + public final static int CATALOG_SITE = 5; + + @XStreamAlias("title") + private String title; + + @XStreamAlias("portrait") + private String portrait; + + @XStreamAlias("url") + private String url; + + @XStreamAlias("body") + private String body; + + @XStreamAlias("author") + private String author; + + @XStreamAlias("authorid") + private int authorId; + + @XStreamAlias("answerCount") + private int answerCount; + + @XStreamAlias("viewCount") + private int viewCount; + + @XStreamAlias("pubDate") + private String pubDate; + + @XStreamAlias("catalog") + private int catalog; + + @XStreamAlias("isnoticeme") + private int isNoticeMe; + + @XStreamAlias("favorite") + private int favorite; + + @XStreamAlias("tags") + private Tags tags; + + @XStreamAlias("answer") + private Answer answer; + + @XStreamAlias("event") + private Event event; + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getPortrait() { + return portrait; + } + + public void setPortrait(String portrait) { + this.portrait = portrait; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public int getAuthorId() { + return authorId; + } + + public void setAuthorId(int authorId) { + this.authorId = authorId; + } + + public int getAnswerCount() { + return answerCount; + } + + public void setAnswerCount(int answerCount) { + this.answerCount = answerCount; + } + + public int getViewCount() { + return viewCount; + } + + public void setViewCount(int viewCount) { + this.viewCount = viewCount; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getCatalog() { + return catalog; + } + + public void setCatalog(int catalog) { + this.catalog = catalog; + } - @XStreamAlias("title") - private String title; - - @XStreamAlias("portrait") - private String portrait; + public int getIsNoticeMe() { + return isNoticeMe; + } - @XStreamAlias("url") - private String url; + public void setIsNoticeMe(int isNoticeMe) { + this.isNoticeMe = isNoticeMe; + } - @XStreamAlias("body") - private String body; + public int getFavorite() { + return favorite; + } - @XStreamAlias("author") - private String author; + public void setFavorite(int favorite) { + this.favorite = favorite; + } - @XStreamAlias("authorid") - private int authorId; + public Post.Tags getTags() { + return tags; + } - @XStreamAlias("answerCount") - private int answerCount; + public void setTags(Tags tags) { + this.tags = tags; + } - @XStreamAlias("viewCount") - private int viewCount; + public Answer getAnswer() { + return answer; + } - @XStreamAlias("pubDate") - private String pubDate; + public void setAnswer(Answer answer) { + this.answer = answer; + } - @XStreamAlias("catalog") - private int catalog; + @XStreamAlias("answer") + public class Answer implements Serializable { - @XStreamAlias("isnoticeme") - private int isNoticeMe; + @XStreamAlias("name") + private String name; - @XStreamAlias("favorite") - private int favorite; + @XStreamAlias("time") + private String time; - @XStreamAlias("tags") - private Tags tags; - - @XStreamAlias("answer") - private Answer answer; - - @XStreamAlias("event") - private Event event; + public String getName() { + return name; + } - public Event getEvent() { - return event; - } + public void setName(String name) { + this.name = name; + } - public void setEvent(Event event) { - this.event = event; - } + public String getTime() { + return time; + } - public String getTitle() { - return title; - } + public void setTime(String time) { + this.time = time; + } + } - public void setTitle(String title) { - this.title = title; - } + public class Tags implements Serializable { + @XStreamImplicit(itemFieldName = "tag") + private List tags; - public String getPortrait() { - return portrait; - } + public List getTags() { + return tags; + } - public void setPortrait(String portrait) { - this.portrait = portrait; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getBody() { - return body; - } - - public void setBody(String body) { - this.body = body; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } - - public int getAuthorId() { - return authorId; - } - - public void setAuthorId(int authorId) { - this.authorId = authorId; - } - - public int getAnswerCount() { - return answerCount; - } - - public void setAnswerCount(int answerCount) { - this.answerCount = answerCount; - } - - public int getViewCount() { - return viewCount; - } - - public void setViewCount(int viewCount) { - this.viewCount = viewCount; - } - - public String getPubDate() { - return pubDate; - } - - public void setPubDate(String pubDate) { - this.pubDate = pubDate; - } - - public int getCatalog() { - return catalog; - } - - public void setCatalog(int catalog) { - this.catalog = catalog; - } - - public int getIsNoticeMe() { - return isNoticeMe; - } - - public void setIsNoticeMe(int isNoticeMe) { - this.isNoticeMe = isNoticeMe; - } - - public int getFavorite() { - return favorite; - } - - public void setFavorite(int favorite) { - this.favorite = favorite; - } - - public Post.Tags getTags() { - return tags; - } - - public void setTags(Tags tags) { - this.tags = tags; - } - - public Answer getAnswer() { - return answer; - } - - public void setAnswer(Answer answer) { - this.answer = answer; - } - - @XStreamAlias("answer") - public class Answer implements Serializable { - - @XStreamAlias("name") - private String name; - - @XStreamAlias("time") - private String time; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getTime() { - return time; - } - - public void setTime(String time) { - this.time = time; - } - } - - public class Tags implements Serializable { - @XStreamImplicit(itemFieldName="tag") - private List tags; - - public List getTags() { - return tags; - } - - public void setTags(List tags) { - this.tags = tags; - } - } + public void setTags(List tags) { + this.tags = tags; + } + } } diff --git a/app/src/main/java/net/oschina/app/bean/PostDetail.java b/app/src/main/java/net/oschina/app/bean/PostDetail.java deleted file mode 100644 index 017c5bf86dd52ef899f320dd245bcd02a9638195..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/PostDetail.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.oschina.app.bean; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 帖子详情 - * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2014年10月11日 下午3:28:33 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class PostDetail extends Entity { - - @XStreamAlias("post") - private Post post; - - public Post getPost() { - return post; - } - - public void setPost(Post post) { - this.post = post; - } - -} diff --git a/app/src/main/java/net/oschina/app/bean/PostList.java b/app/src/main/java/net/oschina/app/bean/PostList.java index 06c848db8b669fc67569401d1bce681ab00973af..db9ebfd0d6059247d8d5b3cd65ad3b0b7c396bbe 100644 --- a/app/src/main/java/net/oschina/app/bean/PostList.java +++ b/app/src/main/java/net/oschina/app/bean/PostList.java @@ -1,42 +1,45 @@ package net.oschina.app.bean; +import com.thoughtworks.xstream.annotations.XStreamAlias; + import java.util.ArrayList; import java.util.List; -import com.thoughtworks.xstream.annotations.XStreamAlias; - /** * 帖子实体类列表 + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年10月9日 下午6:10:11 - * */ @SuppressWarnings("serial") @XStreamAlias("oschina") public class PostList extends Entity implements ListEntity { - - public final static String PREF_READED_POST_LIST = "readed_post_list.pref"; - - @XStreamAlias("pagesize") - private int pageSize; - - @XStreamAlias("postCount") - private int postCount; - - @XStreamAlias("posts") - private List postlist = new ArrayList(); - - public int getPageSize() { - return pageSize; - } - public int getPostCount() { - return postCount; - } - public List getPostlist() { - return postlist; - } - @Override - public List getList() { - return postlist; - } + + public final static String PREF_READED_POST_LIST = "readed_post_list.pref"; + + @XStreamAlias("pagesize") + private int pageSize; + + @XStreamAlias("postCount") + private int postCount; + + @XStreamAlias("posts") + private List postlist = new ArrayList(); + + public int getPageSize() { + return pageSize; + } + + public int getPostCount() { + return postCount; + } + + public List getPostlist() { + return postlist; + } + + @Override + public List getList() { + return postlist; + } } diff --git a/app/src/main/java/net/oschina/app/bean/Report.java b/app/src/main/java/net/oschina/app/bean/Report.java index 02c7e9ccaeb6fb31008d8793c32f0b66168f6069..3eaf6e932d25937d09295902a84aa0b111767050 100644 --- a/app/src/main/java/net/oschina/app/bean/Report.java +++ b/app/src/main/java/net/oschina/app/bean/Report.java @@ -2,7 +2,7 @@ package net.oschina.app.bean; /** * 举报实体类 - * + * * @author 火蚁(http://my.oschina.net/LittleDY) * @version 1.0 * @created 2014-02-13 @@ -10,19 +10,21 @@ package net.oschina.app.bean; public class Report extends Entity { private static final long serialVersionUID = 1L; + public static final byte TYPE_ARTICLE = 0x09; public static final byte TYPE_QUESTION = 0x02;// 问题 + public static final byte TYPE_BLOG = 0x03; - private int objId;//需要举报的id + private long objId;//需要举报的id private String url;// 举报的链接地址 private byte objType;// 举报的类型 private int reason;// 原因 private String otherReason;// 其他原因 - public int getObjId() { + public long getObjId() { return objId; } - public void setObjId(int objId) { + public void setObjId(long objId) { this.objId = objId; } diff --git a/app/src/main/java/net/oschina/app/bean/Result.java b/app/src/main/java/net/oschina/app/bean/Result.java index 3f0a4eeab2d944dd36d23bb77f675ec6f11361f7..0712e07b993a1f1019dc727f959ef802d923c044 100644 --- a/app/src/main/java/net/oschina/app/bean/Result.java +++ b/app/src/main/java/net/oschina/app/bean/Result.java @@ -1,12 +1,12 @@ package net.oschina.app.bean; -import java.io.Serializable; - import com.thoughtworks.xstream.annotations.XStreamAlias; +import java.io.Serializable; + /** * 数据操作结果实体类 - * + * * @author liux (http://my.oschina.net/liux) * @version 1.0 * @created 2012-3-21 @@ -22,22 +22,22 @@ public class Result implements Serializable { private String errorMessage; public boolean OK() { - return errorCode == 1; + return errorCode == 1; } public int getErrorCode() { - return errorCode; + return errorCode; } public void setErrorCode(int errorCode) { - this.errorCode = errorCode; + this.errorCode = errorCode; } public String getErrorMessage() { - return errorMessage; + return errorMessage; } public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; + this.errorMessage = errorMessage; } } diff --git a/app/src/main/java/net/oschina/app/bean/ResultBean.java b/app/src/main/java/net/oschina/app/bean/ResultBean.java index 687c82a5a197ea4e11d2cb6ddd43e0aa0e702d48..060c6a10275e5912538c43b821fffe008572d1c7 100644 --- a/app/src/main/java/net/oschina/app/bean/ResultBean.java +++ b/app/src/main/java/net/oschina/app/bean/ResultBean.java @@ -4,10 +4,9 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 操作结果实体类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @version 创建时间:2014年10月14日 下午2:59:27 - * */ @SuppressWarnings("serial") @XStreamAlias("oschina") @@ -30,41 +29,41 @@ public class ResultBean extends Base { private int relation; public Result getResult() { - return result; + return result; } public void setResult(Result result) { - this.result = result; + this.result = result; } public int getRelation() { - return relation; + return relation; } public void setRelation(int relation) { - this.relation = relation; + this.relation = relation; } public Notice getNotice() { - return notice; + return notice; } public void setNotice(Notice notice) { - this.notice = notice; + this.notice = notice; } public Comment getComment() { - return comment; + return comment; } public void setComment(Comment comment) { - this.comment = comment; + this.comment = comment; } public MessageDetail getMessage() { //现在pub_message接口返回的是comment对象。所以要转成message message = new MessageDetail(); - if(comment!=null) { + if (comment != null) { message.setId(comment.getId()); message.setPortrait(comment.getPortrait()); message.setAuthor(comment.getAuthor()); diff --git a/app/src/main/java/net/oschina/app/bean/SearchList.java b/app/src/main/java/net/oschina/app/bean/SearchList.java deleted file mode 100644 index 4e212dd92d8dd5be3a5b38b074c64d9e9005b3fd..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/SearchList.java +++ /dev/null @@ -1,43 +0,0 @@ -package net.oschina.app.bean; - -import java.util.ArrayList; -import java.util.List; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 搜索实体类 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年12月5日 上午11:19:44 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class SearchList extends Entity implements ListEntity { - - public final static String CATALOG_ALL = "all"; - public final static String CATALOG_NEWS = "news"; - public final static String CATALOG_POST = "post"; - public final static String CATALOG_SOFTWARE = "software"; - public final static String CATALOG_BLOG = "blog"; - - @XStreamAlias("pagesize") - private int pageSize; - - @XStreamAlias("results") - private List list = new ArrayList(); - - public int getPageSize() { - return pageSize; - } - - @Override - public List getList() { - return list; - } - - public void setList(List list) { - this.list = list; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/SearchResult.java b/app/src/main/java/net/oschina/app/bean/SearchResult.java deleted file mode 100644 index e414f412cc8bd3dceeaa9ea77eac6b22204b71cb..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/SearchResult.java +++ /dev/null @@ -1,88 +0,0 @@ -package net.oschina.app.bean; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 搜索结果实体类 - */ -@SuppressWarnings("serial") -@XStreamAlias("result") -public class SearchResult extends Entity { - - @XStreamAlias("objid") - private int id; - - @XStreamAlias("type") - private String type; - - @XStreamAlias("title") - private String title; - - @XStreamAlias("description") - private String description; - - @XStreamAlias("url") - private String url; - - @XStreamAlias("pubDate") - private String pubDate; - - @XStreamAlias("author") - private String author; - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getPubDate() { - return pubDate; - } - - public void setPubDate(String pubDate) { - this.pubDate = pubDate; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/ShakeObject.java b/app/src/main/java/net/oschina/app/bean/ShakeObject.java deleted file mode 100644 index 865de1269247c1c048daf715320416613c1721da..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/ShakeObject.java +++ /dev/null @@ -1,113 +0,0 @@ -package net.oschina.app.bean; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -@XStreamAlias("oschina") -public class ShakeObject { - - @XStreamAlias("randomtype") - private String randomtype; // 数据类型 - @XStreamAlias("id") - private String id; // 数据id - @XStreamAlias("title") - private String title; // 帖子标题 - @XStreamAlias("detail") - private String detail; // 内容 - @XStreamAlias("author") - private String author; // 作者 - @XStreamAlias("authorid") - private String authorid; // 作者id - @XStreamAlias("image") - private String image; // 头像地址 - @XStreamAlias("pubDate") - private String pubDate; // 收录日期 - @XStreamAlias("commentCount") - private String commentCount; - @XStreamAlias("url") - private String url; - - public static final String RANDOMTYPE_NEWS = "1"; - public static final String RANDOMTYPE_BLOG = "2"; - public static final String RANDOMTYPE_SOFTWARE = "3"; - - public String getRandomtype() { - return randomtype; - } - - public void setRandomtype(String randomtype) { - this.randomtype = randomtype; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDetail() { - return detail; - } - - public void setDetail(String detail) { - this.detail = detail; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } - - public String getAuthorid() { - return authorid; - } - - public void setAuthorid(String authorid) { - this.authorid = authorid; - } - - public String getImage() { - return image; - } - - public void setImage(String image) { - this.image = image; - } - - public String getPubDate() { - return pubDate; - } - - public void setPubDate(String pubDate) { - this.pubDate = pubDate; - } - - public String getCommentCount() { - return commentCount; - } - - public void setCommentCount(String commentCount) { - this.commentCount = commentCount; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - -} diff --git a/app/src/main/java/net/oschina/app/bean/SimpleBackPage.java b/app/src/main/java/net/oschina/app/bean/SimpleBackPage.java index 7e40b7f95860bac0148e44ddb1cd0b141f4ac102..c3b31ebea046908b8c1b66885fc50abf29ec8799 100644 --- a/app/src/main/java/net/oschina/app/bean/SimpleBackPage.java +++ b/app/src/main/java/net/oschina/app/bean/SimpleBackPage.java @@ -2,25 +2,16 @@ package net.oschina.app.bean; import net.oschina.app.R; import net.oschina.app.fragment.AboutOSCFragment; -import net.oschina.app.fragment.ActiveFragment; import net.oschina.app.fragment.BrowserFragment; -import net.oschina.app.fragment.CommentFrament; import net.oschina.app.fragment.EventAppliesFragment; import net.oschina.app.fragment.EventFragment; -import net.oschina.app.fragment.FeedBackFragment; -import net.oschina.app.fragment.MessageDetailFragment; -import net.oschina.app.fragment.MyInformationFragment; import net.oschina.app.fragment.MyInformationFragmentDetail; import net.oschina.app.fragment.QuestionTagFragment; import net.oschina.app.fragment.SettingsFragment; -import net.oschina.app.fragment.SettingsNotificationFragment; -import net.oschina.app.fragment.SoftWareTweetsFrament; -import net.oschina.app.fragment.TweetLikeUsersFragment; -import net.oschina.app.fragment.TweetPubFragment; -import net.oschina.app.fragment.TweetRecordFragment; -import net.oschina.app.fragment.TweetsFragment; -import net.oschina.app.fragment.UserBlogFragment; -import net.oschina.app.fragment.UserCenterFragment; +import net.oschina.app.improve.main.subscription.SubFragment; +import net.oschina.app.improve.tweet.fragments.TweetFragment; +import net.oschina.app.improve.user.fragments.UserBlogFragment; +import net.oschina.app.improve.user.fragments.UserQuestionFragment; import net.oschina.app.team.fragment.NoteBookFragment; import net.oschina.app.team.fragment.NoteEditFragment; import net.oschina.app.team.fragment.TeamActiveFragment; @@ -34,68 +25,23 @@ import net.oschina.app.team.viewpagefragment.MyIssuePagerfragment; import net.oschina.app.team.viewpagefragment.TeamDiaryFragment; import net.oschina.app.team.viewpagefragment.TeamIssueViewPageFragment; import net.oschina.app.team.viewpagefragment.TeamProjectViewPagerFragment; -import net.oschina.app.viewpagerfragment.BlogViewPagerFragment; import net.oschina.app.viewpagerfragment.EventViewPagerFragment; -import net.oschina.app.viewpagerfragment.FriendsViewPagerFragment; -import net.oschina.app.viewpagerfragment.NoticeViewPagerFragment; -import net.oschina.app.viewpagerfragment.OpensourceSoftwareFragment; -import net.oschina.app.viewpagerfragment.QuestViewPagerFragment; -import net.oschina.app.viewpagerfragment.SearchViewPageFragment; -import net.oschina.app.viewpagerfragment.UserFavoriteViewPagerFragment; +import net.oschina.app.viewpagerfragment.OpenSoftwareFragment; public enum SimpleBackPage { - COMMENT(1, R.string.actionbar_title_comment, CommentFrament.class), - - QUEST(2, R.string.actionbar_title_questions, QuestViewPagerFragment.class), - - TWEET_PUB(3, R.string.actionbar_title_tweetpub, TweetPubFragment.class), - - SOFTWARE_TWEETS(4, R.string.actionbar_title_softtweet, - SoftWareTweetsFrament.class), - - USER_CENTER(5, R.string.actionbar_title_user_center, - UserCenterFragment.class), - USER_BLOG(6, R.string.actionbar_title_user_blog, UserBlogFragment.class), - MY_INFORMATION(7, R.string.actionbar_title_my_information, - MyInformationFragment.class), - - MY_ACTIVE(8, R.string.actionbar_title_active, ActiveFragment.class), - - MY_MES(9, R.string.actionbar_title_mes, NoticeViewPagerFragment.class), - - OPENSOURCE_SOFTWARE(10, R.string.actionbar_title_softwarelist, - OpensourceSoftwareFragment.class), - - MY_FRIENDS(11, R.string.actionbar_title_my_friends, - FriendsViewPagerFragment.class), + OPEN_SOURCE_SOFTWARE(10, R.string.actionbar_title_software_list, + OpenSoftwareFragment.class), QUESTION_TAG(12, R.string.actionbar_title_question, QuestionTagFragment.class), - MESSAGE_DETAIL(13, R.string.actionbar_title_message_detail, - MessageDetailFragment.class), - - USER_FAVORITE(14, R.string.actionbar_title_user_favorite, - UserFavoriteViewPagerFragment.class), - SETTING(15, R.string.actionbar_title_setting, SettingsFragment.class), - SETTING_NOTIFICATION(16, R.string.actionbar_title_setting_notification, - SettingsNotificationFragment.class), - ABOUT_OSC(17, R.string.actionbar_title_about_osc, AboutOSCFragment.class), - BLOG(18, R.string.actionbar_title_blog_area, BlogViewPagerFragment.class), - - RECORD(19, R.string.actionbar_title_tweetpub, TweetRecordFragment.class), - - SEARCH(20, R.string.actionbar_title_search, SearchViewPageFragment.class), - - EVENT_LIST(21, R.string.actionbar_title_event, EventViewPagerFragment.class), - EVENT_APPLY(22, R.string.actionbar_title_event_apply, EventAppliesFragment.class), @@ -103,7 +49,7 @@ public enum SimpleBackPage { NOTE(24, R.string.actionbar_title_note, NoteBookFragment.class), - NOTE_EDIT(25, R.string.actionbar_title_noteedit, NoteEditFragment.class), + NOTE_EDIT(25, R.string.actionbar_title_note_edit, NoteEditFragment.class), BROWSER(26, R.string.app_name, BrowserFragment.class), @@ -112,9 +58,7 @@ public enum SimpleBackPage { MY_INFORMATION_DETAIL(28, R.string.actionbar_title_my_information, MyInformationFragmentDetail.class), - FEED_BACK(29, R.string.str_feedback_title, FeedBackFragment.class), - - TEAM_USER_INFO(30, R.string.str_team_userinfo, + TEAM_USER_INFO(30, R.string.str_team_user_info, TeamMemberInformationFragment.class), MY_ISSUE_PAGER(31, R.string.str_team_my_issue, MyIssuePagerfragment.class), @@ -137,9 +81,13 @@ public enum SimpleBackPage { TEAM_PROJECT(40, R.string.team_project, TeamProjectFragment.class), - TWEET_LIKE_USER_LIST(41, 0, TweetLikeUsersFragment.class), + TWEET_TOPIC_LIST(42, R.string.topic_list, TweetFragment.class), + + MY_EVENT(43, R.string.actionbar_title_my_event, EventViewPagerFragment.class), + + MY_QUESTION(44, R.string.question, UserQuestionFragment.class), - TWEET_TOPIC_LIST(42, 0, TweetsFragment.class); + OUTLINE_EVENTS(45, R.string.event_type_outline, SubFragment.class); private int title; private Class clz; @@ -182,4 +130,6 @@ public enum SimpleBackPage { } return null; } + + } diff --git a/app/src/main/java/net/oschina/app/bean/SingInResult.java b/app/src/main/java/net/oschina/app/bean/SingInResult.java index b4396268dde021d4043174c15b0599877d84450e..98b4c9792fbac9238904ba4641c9ed98b9ec6796 100644 --- a/app/src/main/java/net/oschina/app/bean/SingInResult.java +++ b/app/src/main/java/net/oschina/app/bean/SingInResult.java @@ -7,61 +7,61 @@ import org.json.JSONObject; /** * 签到返回结果实体类 + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年10月22日 下午1:43:13 - * */ public class SingInResult { - public final static String NODE_MES = "msg"; - public final static String NODE_ERROR = "error"; + public final static String NODE_MES = "msg"; + public final static String NODE_ERROR = "error"; - private String message;// 成功消息 - private String errorMes;// 错误消息 - private boolean ok;// 是否成功 + private String message;// 成功消息 + private String errorMes;// 错误消息 + private boolean ok;// 是否成功 - public String getMessage() { - return message; - } + public String getMessage() { + return message; + } - public void setMessage(String message) { - this.message = message; - } + public void setMessage(String message) { + this.message = message; + } - public String getErrorMes() { - return errorMes; - } + public String getErrorMes() { + return errorMes; + } - public void setErrorMes(String errorMes) { - this.errorMes = errorMes; - } + public void setErrorMes(String errorMes) { + this.errorMes = errorMes; + } - public boolean isOk() { - return ok; - } + public boolean isOk() { + return ok; + } - public void setOk(boolean ok) { - this.ok = ok; - } + public void setOk(boolean ok) { + this.ok = ok; + } - public static SingInResult parse(String jsonStr) throws AppException { - SingInResult jsonResult = new SingInResult(); - try { - JSONObject jsonObject = new JSONObject(jsonStr); - // 如果有错误信息则表示不成功 - if (jsonObject.isNull(NODE_ERROR)) { - jsonResult.setOk(true); - } - if (!jsonObject.isNull(NODE_ERROR)) { - jsonResult.setErrorMes(jsonObject.getString(NODE_ERROR)); - } - if (!jsonObject.isNull(NODE_MES)) { - jsonResult.setMessage(jsonObject.getString(NODE_MES)); - } - } catch (JSONException e) { - // 抛出一个json解析错误的异常 - throw AppException.json(e); - } - return jsonResult; - } + public static SingInResult parse(String jsonStr) throws AppException { + SingInResult jsonResult = new SingInResult(); + try { + JSONObject jsonObject = new JSONObject(jsonStr); + // 如果有错误信息则表示不成功 + if (jsonObject.isNull(NODE_ERROR)) { + jsonResult.setOk(true); + } + if (!jsonObject.isNull(NODE_ERROR)) { + jsonResult.setErrorMes(jsonObject.getString(NODE_ERROR)); + } + if (!jsonObject.isNull(NODE_MES)) { + jsonResult.setMessage(jsonObject.getString(NODE_MES)); + } + } catch (JSONException e) { + // 抛出一个json解析错误的异常 + throw AppException.json(e); + } + return jsonResult; + } } diff --git a/app/src/main/java/net/oschina/app/bean/Software.java b/app/src/main/java/net/oschina/app/bean/Software.java deleted file mode 100644 index 3564a489fba6e43917d25b4b4be13b8aea0f79a0..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/Software.java +++ /dev/null @@ -1,202 +0,0 @@ -package net.oschina.app.bean; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 软件实体类 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年10月23日 下午3:03:25 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("software") -public class Software extends Entity { - - @XStreamAlias("title") - private String title; - - @XStreamAlias("author") - private String author; - - @XStreamAlias("authorid") - private int authorId; - - @XStreamAlias("recommended") - private int recommended; - - @XStreamAlias("extensiontitle") - private String extensionTitle; - - @XStreamAlias("license") - private String license; - - @XStreamAlias("body") - private String body; - - @XStreamAlias("homepage") - private String homepage; - - @XStreamAlias("document") - private String document; - - @XStreamAlias("download") - private String download; - - @XStreamAlias("logo") - private String logo; - - @XStreamAlias("language") - private String language; - - @XStreamAlias("os") - private String os; - - @XStreamAlias("recordtime") - private String recordtime; - - @XStreamAlias("url") - private String url; - - @XStreamAlias("favorite") - private int favorite; - - @XStreamAlias("tweetCount") - private int tweetCount; - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getAuthor() { - return author; - } - - public void setAuthor(String author) { - this.author = author; - } - - public int getAuthorId() { - return authorId; - } - - public void setAuthorId(int authorId) { - this.authorId = authorId; - } - - public int getRecommended() { - return recommended; - } - - public void setRecommended(int recommended) { - this.recommended = recommended; - } - - public String getExtensionTitle() { - return extensionTitle; - } - - public void setExtensionTitle(String extensionTitle) { - this.extensionTitle = extensionTitle; - } - - public String getLicense() { - return license; - } - - public void setLicense(String license) { - this.license = license; - } - - public String getBody() { - return body; - } - - public void setBody(String body) { - this.body = body; - } - - public String getHomepage() { - return homepage; - } - - public void setHomepage(String homepage) { - this.homepage = homepage; - } - - public String getDocument() { - return document; - } - - public void setDocument(String document) { - this.document = document; - } - - public String getDownload() { - return download; - } - - public void setDownload(String download) { - this.download = download; - } - - public String getLogo() { - return logo; - } - - public void setLogo(String logo) { - this.logo = logo; - } - - public String getLanguage() { - return language; - } - - public void setLanguage(String language) { - this.language = language; - } - - public String getOs() { - return os; - } - - public void setOs(String os) { - this.os = os; - } - - public String getRecordtime() { - return recordtime; - } - - public void setRecordtime(String recordtime) { - this.recordtime = recordtime; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public int getFavorite() { - return favorite; - } - - public void setFavorite(int favorite) { - this.favorite = favorite; - } - - public int getTweetCount() { - return tweetCount; - } - - public void setTweetCount(int tweetCount) { - this.tweetCount = tweetCount; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/SoftwareCatalogList.java b/app/src/main/java/net/oschina/app/bean/SoftwareCatalogList.java index c14e52a514a43f7e12b26a5bd5931239a556bad0..beadeee5e4d73f28a6cfe0db49330984e9eb3b72 100644 --- a/app/src/main/java/net/oschina/app/bean/SoftwareCatalogList.java +++ b/app/src/main/java/net/oschina/app/bean/SoftwareCatalogList.java @@ -1,67 +1,70 @@ package net.oschina.app.bean; +import com.thoughtworks.xstream.annotations.XStreamAlias; + import java.util.ArrayList; import java.util.List; -import com.thoughtworks.xstream.annotations.XStreamAlias; - /** * 开源软件分类列表实体 + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年12月2日 上午10:54:10 - * */ @SuppressWarnings("serial") @XStreamAlias("oschina") public class SoftwareCatalogList extends Entity implements ListEntity { - - @XStreamAlias("softwarecount") - private int softwarecount; - @XStreamAlias("softwareTypes") - private List softwarecataloglist = new ArrayList(); - - public int getSoftwarecount() { - return softwarecount; - } - - public void setSoftwarecount(int softwarecount) { - this.softwarecount = softwarecount; - } - - public List getSoftwarecataloglist() { - return softwarecataloglist; - } - - public void setSoftwarecataloglist(List softwarecataloglist) { - this.softwarecataloglist = softwarecataloglist; - } - - @Override - public List getList() { - return softwarecataloglist; - } - - @XStreamAlias("softwareType") - public class SoftwareType extends Entity { - - @XStreamAlias("name") - private String name; - @XStreamAlias("tag") - private int tag; - - public String getName() { - return name; - } - public void setName(String name) { - this.name = name; - } - public int getTag() { - return tag; - } - public void setTag(int tag) { - this.tag = tag; - } - - } + + @XStreamAlias("softwarecount") + private int softwarecount; + @XStreamAlias("softwareTypes") + private List softwarecataloglist = new ArrayList(); + + public int getSoftwarecount() { + return softwarecount; + } + + public void setSoftwarecount(int softwarecount) { + this.softwarecount = softwarecount; + } + + public List getSoftwarecataloglist() { + return softwarecataloglist; + } + + public void setSoftwarecataloglist(List softwarecataloglist) { + this.softwarecataloglist = softwarecataloglist; + } + + @Override + public List getList() { + return softwarecataloglist; + } + + @XStreamAlias("softwareType") + public class SoftwareType extends Entity { + + @XStreamAlias("name") + private String name; + @XStreamAlias("tag") + private int tag; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getTag() { + return tag; + } + + public void setTag(int tag) { + this.tag = tag; + } + + } } diff --git a/app/src/main/java/net/oschina/app/bean/SoftwareDec.java b/app/src/main/java/net/oschina/app/bean/SoftwareDec.java index 74db5284a39633901ba3996ecff7238bbfd44a7d..d3252cbc0854d5d999bef8c94ca901a1739e1a5c 100644 --- a/app/src/main/java/net/oschina/app/bean/SoftwareDec.java +++ b/app/src/main/java/net/oschina/app/bean/SoftwareDec.java @@ -4,42 +4,41 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 软件列表 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2015年1月20日 下午3:34:52 - * */ @SuppressWarnings("serial") @XStreamAlias("software") public class SoftwareDec extends Entity { - @XStreamAlias("name") - private String name; - @XStreamAlias("description") - private String description; - @XStreamAlias("url") - private String url; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } + @XStreamAlias("name") + private String name; + @XStreamAlias("description") + private String description; + @XStreamAlias("url") + private String url; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } } diff --git a/app/src/main/java/net/oschina/app/bean/SoftwareDetail.java b/app/src/main/java/net/oschina/app/bean/SoftwareDetail.java deleted file mode 100644 index b4d7689a18a78b6f71c6bc7b391b09efa65425b5..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/SoftwareDetail.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.oschina.app.bean; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 软件详情实体类 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2014年10月23日 下午3:10:54 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class SoftwareDetail extends Entity { - - @XStreamAlias("software") - private Software software; - - public Software getSoftware() { - return software; - } - - public void setSoftware(Software software) { - this.software = software; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/SoftwareList.java b/app/src/main/java/net/oschina/app/bean/SoftwareList.java index bc5bd6e21e20510986b2ec668315dc154edcdd16..acf756ab2e0150921f0db87c710b327d73979dfd 100644 --- a/app/src/main/java/net/oschina/app/bean/SoftwareList.java +++ b/app/src/main/java/net/oschina/app/bean/SoftwareList.java @@ -1,54 +1,54 @@ package net.oschina.app.bean; +import com.thoughtworks.xstream.annotations.XStreamAlias; + import java.util.ArrayList; import java.util.List; -import com.thoughtworks.xstream.annotations.XStreamAlias; - @SuppressWarnings("serial") @XStreamAlias("oschina") public class SoftwareList extends Entity implements ListEntity { - - public final static String PREF_READED_SOFTWARE_LIST = "readed_software_list.pref"; - - public final static String CATALOG_RECOMMEND = "recommend"; - public final static String CATALOG_TIME = "time"; - public final static String CATALOG_VIEW = "view"; - public final static String CATALOG_LIST_CN = "list_cn"; - - @XStreamAlias("softwarecount") - private int softwarecount; - @XStreamAlias("pagesize") - private int pagesize; - @XStreamAlias("softwares") - private List softwarelist = new ArrayList(); - - public int getSoftwarecount() { - return softwarecount; - } - - public void setSoftwarecount(int softwarecount) { - this.softwarecount = softwarecount; - } - - public int getPagesize() { - return pagesize; - } - - public void setPagesize(int pagesize) { - this.pagesize = pagesize; - } - - public List getSoftwarelist() { - return softwarelist; - } - - public void setSoftwarelist(List softwarelist) { - this.softwarelist = softwarelist; - } - - @Override - public List getList() { - return softwarelist; - } + + public final static String PREF_READED_SOFTWARE_LIST = "readed_software_list.pref"; + + public final static String CATALOG_RECOMMEND = "recommend"; + public final static String CATALOG_TIME = "time"; + public final static String CATALOG_VIEW = "view"; + public final static String CATALOG_LIST_CN = "list_cn"; + + @XStreamAlias("softwarecount") + private int softwarecount; + @XStreamAlias("pagesize") + private int pagesize; + @XStreamAlias("softwares") + private List softwarelist = new ArrayList(); + + public int getSoftwarecount() { + return softwarecount; + } + + public void setSoftwarecount(int softwarecount) { + this.softwarecount = softwarecount; + } + + public int getPagesize() { + return pagesize; + } + + public void setPagesize(int pagesize) { + this.pagesize = pagesize; + } + + public List getSoftwarelist() { + return softwarelist; + } + + public void setSoftwarelist(List softwarelist) { + this.softwarelist = softwarelist; + } + + @Override + public List getList() { + return softwarelist; + } } diff --git a/app/src/main/java/net/oschina/app/bean/Tweet.java b/app/src/main/java/net/oschina/app/bean/Tweet.java index 38de54d17f46ea5408247b537d4693e7888a253e..20adf39093dc5a5c97b681b4646ec3ab6c69ba24 100644 --- a/app/src/main/java/net/oschina/app/bean/Tweet.java +++ b/app/src/main/java/net/oschina/app/bean/Tweet.java @@ -1,7 +1,6 @@ package net.oschina.app.bean; import android.content.Context; -import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.SpannableString; @@ -15,8 +14,7 @@ import android.widget.TextView.BufferType; import com.thoughtworks.xstream.annotations.XStreamAlias; -import net.oschina.app.AppContext; -import net.oschina.app.base.BaseListFragment; +import net.oschina.app.improve.account.AccountHelper; import net.oschina.app.util.UIHelper; import java.util.ArrayList; @@ -85,6 +83,7 @@ public class Tweet extends Entity implements Parcelable { imageFilePath = dest.readString(); audioPath = dest.readString(); isLike = dest.readInt(); + likeCount = dest.readInt(); } public String getAttach() { @@ -228,6 +227,7 @@ public class Tweet extends Entity implements Parcelable { dest.writeString(imageFilePath); dest.writeString(audioPath); dest.writeInt(isLike); + dest.writeInt(likeCount); } public static final Parcelable.Creator CREATOR = new Creator() { @@ -256,6 +256,7 @@ public class Tweet extends Entity implements Parcelable { likeUser.setVisibility(View.GONE); likeUser.setText(""); } + likeUser.setVisibility(View.GONE); } /** @@ -274,12 +275,11 @@ public class Tweet extends Entity implements Parcelable { if (getIsLike() == 1) { for (int i = 0; i < getLikeUser().size(); i++) { - if (getLikeUser().get(i).getId() == AppContext.getInstance() - .getLoginUid()) { + if (getLikeUser().get(i).getId() == AccountHelper.getUserId()) { getLikeUser().remove(i); } } - getLikeUser().add(0, AppContext.getInstance().getLoginUser()); + //getLikeUser().add(0, AccountHelper.getUser()); } for (int i = 0; i < showCunt; i++) { @@ -332,10 +332,7 @@ public class Tweet extends Entity implements Parcelable { @Override public void onClick(View widget) { - Bundle bundle = new Bundle(); - bundle.putInt(BaseListFragment.BUNDLE_KEY_CATALOG, getId()); - UIHelper.showSimpleBack(context, - SimpleBackPage.TWEET_LIKE_USER_LIST, bundle); + } @Override @@ -352,4 +349,5 @@ public class Tweet extends Entity implements Parcelable { return ssb.append("觉得很赞"); } } + } diff --git a/app/src/main/java/net/oschina/app/bean/TweetDetail.java b/app/src/main/java/net/oschina/app/bean/TweetDetail.java deleted file mode 100644 index fbb850a4bc5dbfa486da4645f16fbfa1ac27afc4..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/TweetDetail.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.oschina.app.bean; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * @author HuangWenwei - * - * @date 2014年10月16日 - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class TweetDetail extends Entity { - - @XStreamAlias("tweet") - private Tweet tweet; - - public Tweet getTweet() { - return tweet; - } - public void setTweet(Tweet tweet) { - this.tweet = tweet; - } -} diff --git a/app/src/main/java/net/oschina/app/bean/TweetLike.java b/app/src/main/java/net/oschina/app/bean/TweetLike.java index 2faf97d670d7374ad145ad36e7a0b37e1a70ba1b..2bea376649667d11db5e0a9c2c226815c9013144 100644 --- a/app/src/main/java/net/oschina/app/bean/TweetLike.java +++ b/app/src/main/java/net/oschina/app/bean/TweetLike.java @@ -5,24 +5,23 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 点赞消息实体类 * LikeTweet.java - * - * @author 火蚁(http://my.oschina.net/u/253900) * + * @author 火蚁(http://my.oschina.net/u/253900) * @data 2015-4-10 上午10:09:15 */ @SuppressWarnings("serial") @XStreamAlias("mytweet") public class TweetLike extends Entity { - + @XStreamAlias("user") private User user; - + @XStreamAlias("tweet") private Tweet tweet; - + @XStreamAlias("datatime") private String datatime; - + @XStreamAlias("appclient") private int appClient; @@ -57,5 +56,7 @@ public class TweetLike extends Entity { public void setAppClient(int appClient) { this.appClient = appClient; } + + } diff --git a/app/src/main/java/net/oschina/app/bean/TweetLikeList.java b/app/src/main/java/net/oschina/app/bean/TweetLikeList.java deleted file mode 100644 index aa22120703357cbf9547feb62aa368730d89ee55..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/TweetLikeList.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.oschina.app.bean; - -import java.util.List; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * LikeTweetList.java - * - * @author 火蚁(http://my.oschina.net/u/253900) - * - * @data 2015-4-10 上午10:11:48 - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class TweetLikeList implements ListEntity { - - @XStreamAlias("likeList") - private List list; - - @Override - public List getList() { - // TODO Auto-generated method stub - return list; - } - - public void setList(List list) { - this.list = list; - } - -} - diff --git a/app/src/main/java/net/oschina/app/bean/TweetLikeUserList.java b/app/src/main/java/net/oschina/app/bean/TweetLikeUserList.java deleted file mode 100644 index df3ee1c359d03d14f828f027f31173bda91ff213..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/TweetLikeUserList.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.oschina.app.bean; - -import java.util.ArrayList; -import java.util.List; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * TweetLikeUserList.java - * - * @author 火蚁(http://my.oschina.net/u/253900) - * - * @data 2015-3-26 下午4:23:32 - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class TweetLikeUserList implements ListEntity { - - @XStreamAlias("likeList") - private List list = new ArrayList(); - - public List getList() { - return list; - } - - public void getList(List list) { - this.list = list; - } -} - diff --git a/app/src/main/java/net/oschina/app/bean/TweetsList.java b/app/src/main/java/net/oschina/app/bean/TweetsList.java deleted file mode 100644 index 5e633868cd38a17e4559450c1c90798a773ecb71..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/TweetsList.java +++ /dev/null @@ -1,57 +0,0 @@ -package net.oschina.app.bean; - -import java.util.ArrayList; -import java.util.List; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * @author HuangWenwei - * - * @date 2014年10月10日 - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class TweetsList extends Entity implements ListEntity { - - public final static int CATALOG_LATEST = 0; - public final static int CATALOG_HOT = -1; - public final static int CATALOG_ME = 1; - - @XStreamAlias("tweetcount") - private int tweetCount; - @XStreamAlias("pagesize") - private int pagesize; - @XStreamAlias("tweets") - private List tweetslist = new ArrayList(); - - public int getTweetCount() { - return tweetCount; - } - - public void setTweetCount(int tweetCount) { - this.tweetCount = tweetCount; - } - - public int getPagesize() { - return pagesize; - } - - public void setPagesize(int pagesize) { - this.pagesize = pagesize; - } - - public List getTweetslist() { - return tweetslist; - } - - public void setTweetslist(List tweetslist) { - this.tweetslist = tweetslist; - } - - @Override - public List getList() { - return tweetslist; - } - -} diff --git a/app/src/main/java/net/oschina/app/bean/Update.java b/app/src/main/java/net/oschina/app/bean/Update.java deleted file mode 100644 index bbc0d8d9fb197a3fb8eeb413decf79e46f323e9e..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/Update.java +++ /dev/null @@ -1,146 +0,0 @@ -package net.oschina.app.bean; - -import java.io.Serializable; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 更新实体类 - * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2014年11月10日 下午12:56:27 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class Update implements Serializable { - - @XStreamAlias("update") - private UpdateBean update; - - public UpdateBean getUpdate() { - return update; - } - - public void setUpdate(UpdateBean update) { - this.update = update; - } - - @XStreamAlias("update") - public class UpdateBean implements Serializable { - @XStreamAlias("wp7") - private String wp7; - @XStreamAlias("ios") - private String ios; - @XStreamAlias("android") - private AndroidBean android; - - public String getWp7() { - return wp7; - } - - public void setWp7(String wp7) { - this.wp7 = wp7; - } - - public String getIos() { - return ios; - } - - public void setIos(String ios) { - this.ios = ios; - } - - public AndroidBean getAndroid() { - return android; - } - - public void setAndroid(AndroidBean android) { - this.android = android; - } - - } - - @XStreamAlias("android") - public class AndroidBean implements Serializable { - @XStreamAlias("versionCode") - private int versionCode; - @XStreamAlias("versionName") - private String versionName; - @XStreamAlias("downloadUrl") - private String downloadUrl; - @XStreamAlias("updateLog") - private String updateLog; - @XStreamAlias("coverUpdate") - private String coverUpdate; - @XStreamAlias("coverStartDate") - private String coverStartDate; - @XStreamAlias("coverEndDate") - private String coverEndDate; - @XStreamAlias("coverURL") - private String coverURL; - - public int getVersionCode() { - return versionCode; - } - - public void setVersionCode(int versionCode) { - this.versionCode = versionCode; - } - - public String getVersionName() { - return versionName; - } - - public void setVersionName(String versionName) { - this.versionName = versionName; - } - - public String getDownloadUrl() { - return downloadUrl; - } - - public void setDownloadUrl(String downloadUrl) { - this.downloadUrl = downloadUrl; - } - - public String getUpdateLog() { - return updateLog; - } - - public void setUpdateLog(String updateLog) { - this.updateLog = updateLog; - } - - public String getCoverUpdate() { - return coverUpdate; - } - - public void setCoverUpdate(String coverUpdate) { - this.coverUpdate = coverUpdate; - } - - public String getCoverStartDate() { - return coverStartDate; - } - - public void setCoverStartDate(String coverStartDate) { - this.coverStartDate = coverStartDate; - } - - public String getCoverEndDate() { - return coverEndDate; - } - - public void setCoverEndDate(String coverEndDate) { - this.coverEndDate = coverEndDate; - } - - public String getCoverURL() { - return coverURL; - } - - public void setCoverURL(String coverURL) { - this.coverURL = coverURL; - } - } -} diff --git a/app/src/main/java/net/oschina/app/bean/User.java b/app/src/main/java/net/oschina/app/bean/User.java index 4df29db296301f93791626a75143787722709994..589cb3f68d8f95bef383f20c3e370c8b39e40779 100644 --- a/app/src/main/java/net/oschina/app/bean/User.java +++ b/app/src/main/java/net/oschina/app/bean/User.java @@ -4,7 +4,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias; /** * 登录用户实体类 - * + * * @author liux (http://my.oschina.net/liux) * @version 1.0 * @created 2012-3-21 @@ -16,11 +16,20 @@ public class User extends Entity { public final static int RELATION_ACTION_DELETE = 0x00;// 取消关注 public final static int RELATION_ACTION_ADD = 0x01;// 加关注 + @Deprecated public final static int RELATION_TYPE_BOTH = 0x01;// 双方互为粉丝 + @Deprecated public final static int RELATION_TYPE_FANS_HIM = 0x02;// 你单方面关注他 + @Deprecated public final static int RELATION_TYPE_NULL = 0x03;// 互不关注 + @Deprecated public final static int RELATION_TYPE_FANS_ME = 0x04;// 只有他关注我 + public final static int RELATION_TYPE_APIV2_BOTH = 0x01;// 双方互为粉丝 + public final static int RELATION_TYPE_APIV2_ONLY_FANS_HIM = 0x02;// 你单方面关注他 + public final static int RELATION_TYPE_APIV2_ONLY_FANS_ME = 0x03;// 只有他关注我 + public final static int RELATION_TYPE_APIV2_NULL = 0x04;// 互不关注 + @XStreamAlias("uid") private int id; @@ -73,158 +82,158 @@ public class User extends Entity { private boolean isRememberMe; public int getId() { - return id; + return id; } public void setId(int id) { - this.id = id; + this.id = id; } public String getLocation() { - return location; + return location; } public void setLocation(String location) { - this.location = location; + this.location = location; } public String getName() { - return name; + return name; } public void setName(String name) { - this.name = name; + this.name = name; } public int getFollowers() { - return followers; + return followers; } public void setFollowers(int followers) { - this.followers = followers; + this.followers = followers; } public int getFans() { - return fans; + return fans; } public void setFans(int fans) { - this.fans = fans; + this.fans = fans; } public int getScore() { - return score; + return score; } public void setScore(int score) { - this.score = score; + this.score = score; } public String getPortrait() { - return portrait; + return portrait; } public void setPortrait(String portrait) { - this.portrait = portrait; + this.portrait = portrait; } public String getJointime() { - return jointime; + return jointime; } public void setJointime(String jointime) { - this.jointime = jointime; + this.jointime = jointime; } public String getGender() { - return gender; + return gender; } public void setGender(String gender) { - this.gender = gender; + this.gender = gender; } public String getDevplatform() { - return devplatform; + return devplatform; } public void setDevplatform(String devplatform) { - this.devplatform = devplatform; + this.devplatform = devplatform; } public String getExpertise() { - return expertise; + return expertise; } public void setExpertise(String expertise) { - this.expertise = expertise; + this.expertise = expertise; } public int getRelation() { - return relation; + return relation; } public void setRelation(int relation) { - this.relation = relation; + this.relation = relation; } public String getLatestonline() { - return latestonline; + return latestonline; } public void setLatestonline(String latestonline) { - this.latestonline = latestonline; + this.latestonline = latestonline; } public String getFrom() { - return from; + return from; } public void setFrom(String from) { - this.from = from; + this.from = from; } public int getFavoritecount() { - return favoritecount; + return favoritecount; } public void setFavoritecount(int favoritecount) { - this.favoritecount = favoritecount; + this.favoritecount = favoritecount; } public String getAccount() { - return account; + return account; } public void setAccount(String account) { - this.account = account; + this.account = account; } public String getPwd() { - return pwd; + return pwd; } public void setPwd(String pwd) { - this.pwd = pwd; + this.pwd = pwd; } public boolean isRememberMe() { - return isRememberMe; + return isRememberMe; } public void setRememberMe(boolean isRememberMe) { - this.isRememberMe = isRememberMe; + this.isRememberMe = isRememberMe; } @Override public String toString() { - return "User [uid=" + id + ", location=" + location + ", name=" + name - + ", followers=" + followers + ", fans=" + fans + ", score=" - + score + ", portrait=" + portrait + ", jointime=" + jointime - + ", gender=" + gender + ", devplatform=" + devplatform - + ", expertise=" + expertise + ", relation=" + relation - + ", latestonline=" + latestonline + ", from=" + from - + ", favoritecount=" + favoritecount + ", account=" + account - + ", pwd=" + pwd + ", isRememberMe=" + isRememberMe + "]"; + return "User [uid=" + id + ", location=" + location + ", name=" + name + + ", followers=" + followers + ", fans=" + fans + ", score=" + + score + ", portrait=" + portrait + ", jointime=" + jointime + + ", gender=" + gender + ", devplatform=" + devplatform + + ", expertise=" + expertise + ", relation=" + relation + + ", latestonline=" + latestonline + ", from=" + from + + ", favoritecount=" + favoritecount + ", account=" + account + + ", pwd=" + pwd + ", isRememberMe=" + isRememberMe + "]"; } } diff --git a/app/src/main/java/net/oschina/app/bean/UserInformation.java b/app/src/main/java/net/oschina/app/bean/UserInformation.java deleted file mode 100644 index a23f53ef95afafa1fc62b3c7250c8ef54a6d34cf..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/bean/UserInformation.java +++ /dev/null @@ -1,49 +0,0 @@ -package net.oschina.app.bean; - -import java.util.List; - -import com.thoughtworks.xstream.annotations.XStreamAlias; - -/** - * 个人信息专用实体类 - * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2014年10月29日 上午10:53:54 - * - */ -@SuppressWarnings("serial") -@XStreamAlias("oschina") -public class UserInformation extends Base { - - @XStreamAlias("user") - private User user; - - @XStreamAlias("pagesize") - private int pageSize; - - @XStreamAlias("activies") - private List activeList; - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } - - public int getPageSize() { - return pageSize; - } - - public void setPageSize(int pageSize) { - this.pageSize = pageSize; - } - - public List getActiveList() { - return activeList; - } - - public void setActiveList(List activeList) { - this.activeList = activeList; - } -} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/broadcast/AlarmReceiver.java b/app/src/main/java/net/oschina/app/broadcast/AlarmReceiver.java deleted file mode 100644 index edc1a16e79a2220379846c0b830093949f685019..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/broadcast/AlarmReceiver.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.oschina.app.broadcast; - -import net.oschina.app.service.NoticeUtils; -import net.oschina.app.util.TLog; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -public class AlarmReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - TLog.log("onReceive ->net.oschina.app收到定时获取消息"); - NoticeUtils.requestNotice(context); - } -} diff --git a/app/src/main/java/net/oschina/app/cache/CacheManager.java b/app/src/main/java/net/oschina/app/cache/CacheManager.java index 1b9ebec7c7b8a147edfe2e1a4f687b67cf327ba2..dd18477e0f1b506b7cae6c3f6fc13ebb2ec07000 100644 --- a/app/src/main/java/net/oschina/app/cache/CacheManager.java +++ b/app/src/main/java/net/oschina/app/cache/CacheManager.java @@ -1,5 +1,10 @@ package net.oschina.app.cache; +import android.content.Context; + +import net.oschina.app.util.TDevice; +import net.oschina.common.utils.StreamUtil; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -10,9 +15,6 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; -import net.oschina.app.util.TDevice; -import android.content.Context; - public class CacheManager { // wifi缓存时间为5分钟 @@ -22,105 +24,99 @@ public class CacheManager { /** * 保存对象 - * + * * @param ser * @param file * @throws IOException */ public static boolean saveObject(Context context, Serializable ser, - String file) { - FileOutputStream fos = null; - ObjectOutputStream oos = null; - try { - fos = context.openFileOutput(file, Context.MODE_PRIVATE); - oos = new ObjectOutputStream(fos); - oos.writeObject(ser); - oos.flush(); - return true; - } catch (Exception e) { - e.printStackTrace(); - return false; - } finally { - try { - oos.close(); - } catch (Exception e) { - } - try { - fos.close(); - } catch (Exception e) { - } - } + String file) { + FileOutputStream fos = null; + ObjectOutputStream oos = null; + try { + fos = context.openFileOutput(file, Context.MODE_PRIVATE); + oos = new ObjectOutputStream(fos); + oos.writeObject(ser); + oos.flush(); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } finally { + StreamUtil.close(fos, oos); + } + } + + public static void deleteObject(Context context, String fileName) { + String filePath = context.getFilesDir().getPath() + "/" + fileName; + File file = new File(filePath); + if (file.exists()) { + file.delete(); + } } /** * 读取对象 - * + * * @param file * @return * @throws IOException */ public static Serializable readObject(Context context, String file) { - if (!isExistDataCache(context, file)) - return null; - FileInputStream fis = null; - ObjectInputStream ois = null; - try { - fis = context.openFileInput(file); - ois = new ObjectInputStream(fis); - return (Serializable) ois.readObject(); - } catch (FileNotFoundException e) { - } catch (Exception e) { - e.printStackTrace(); - // 反序列化失败 - 删除缓存文件 - if (e instanceof InvalidClassException) { - File data = context.getFileStreamPath(file); - data.delete(); - } - } finally { - try { - ois.close(); - } catch (Exception e) { - } - try { - fis.close(); - } catch (Exception e) { - } - } - return null; + if (!isExistDataCache(context, file)) + return null; + FileInputStream fis = null; + ObjectInputStream ois = null; + try { + fis = context.openFileInput(file); + ois = new ObjectInputStream(fis); + return (Serializable) ois.readObject(); + } catch (FileNotFoundException e) { + } catch (Exception e) { + e.printStackTrace(); + // 反序列化失败 - 删除缓存文件 + if (e instanceof InvalidClassException) { + File data = context.getFileStreamPath(file); + data.delete(); + } + } finally { + StreamUtil.close(ois, fis); + } + return null; } /** * 判断缓存是否存在 - * + * * @param cachefile * @return */ public static boolean isExistDataCache(Context context, String cachefile) { - if (context == null) - return false; - boolean exist = false; - File data = context.getFileStreamPath(cachefile); - if (data.exists()) - exist = true; - return exist; + if (context == null) + return false; + boolean exist = false; + File data = context.getFileStreamPath(cachefile); + if (data.exists()) + exist = true; + return exist; } /** * 判断缓存是否已经失效 */ public static boolean isCacheDataFailure(Context context, String cachefile) { - File data = context.getFileStreamPath(cachefile); - if (!data.exists()) { + File data = context.getFileStreamPath(cachefile); + if (!data.exists()) { - return false; - } - long existTime = System.currentTimeMillis() - data.lastModified(); - boolean failure = false; - if (TDevice.getNetworkType() == TDevice.NETTYPE_WIFI) { - failure = existTime > wifi_cache_time ? true : false; - } else { - failure = existTime > other_cache_time ? true : false; - } - return failure; + return false; + } + long existTime = System.currentTimeMillis() - data.lastModified(); + boolean failure = false; + if (TDevice.isWifiOpen()) { + failure = existTime > wifi_cache_time; + } else { + failure = existTime > other_cache_time; + } + return failure; } } diff --git a/app/src/main/java/net/oschina/app/cache/DataCleanManager.java b/app/src/main/java/net/oschina/app/cache/DataCleanManager.java index 4c200a976ee5b1322ce2a63f75dfc7be8a89404c..8848c44de6d7ef91c2d7b878857220699488ef2a 100644 --- a/app/src/main/java/net/oschina/app/cache/DataCleanManager.java +++ b/app/src/main/java/net/oschina/app/cache/DataCleanManager.java @@ -1,120 +1,130 @@ package net.oschina.app.cache; -import java.io.File; - import android.content.Context; import android.os.Environment; +import java.io.File; + /** * 数据删除工具类 + * * @author FireAnt(http://my.oschina.net/LittleDY) * @version 创建时间:2014年10月27日 上午10:18:22 - * */ public class DataCleanManager { - - /** - * 清除本应用内部缓存 - * (/data/data/com.xxx.xxx/cache) - * @param context - */ - public static void cleanInternalCache(Context context) { - deleteFilesByDirectory(context.getCacheDir()); - deleteFilesByDirectory(context.getFilesDir()); - } - /** - * 清楚本应用所有数据库 - * (/data/data/com.xxx.xxx/databases) - * @param context - */ - public static void cleanDatabases(Context context) { - deleteFilesByDirectory(new File("/data/data/" - + context.getPackageName() + "/databases")); - } + /** + * 清除本应用内部缓存 + * (/data/data/com.xxx.xxx/cache) + * + * @param context + */ + public static void cleanInternalCache(Context context) { + deleteFilesByDirectory(context.getCacheDir()); + //deleteFilesByDirectory(context.getFilesDir()); + } + + /** + * 清楚本应用所有数据库 + * (/data/data/com.xxx.xxx/databases) + * + * @param context + */ + public static void cleanDatabases(Context context) { + deleteFilesByDirectory(new File("/data/data/" + + context.getPackageName() + "/databases")); + } + + /** + * 清除本应用SharedPreference + * (/data/data/com.xxx.xxx/shared_prefs) + * + * @param context + */ + public static void cleanSharedPreference(Context context) { + deleteFilesByDirectory(new File("/data/data/" + + context.getPackageName() + "/shared_prefs")); + } + + /** + * 按名字清除本应用数据库 + * + * @param context + * @param dbName + */ + public static void cleanDatabaseByName(Context context, String dbName) { + context.deleteDatabase(dbName); + } - /** - * 清除本应用SharedPreference - * (/data/data/com.xxx.xxx/shared_prefs) - * @param context - */ - public static void cleanSharedPreference(Context context) { - deleteFilesByDirectory(new File("/data/data/" - + context.getPackageName() + "/shared_prefs")); - } - - /** - * 按名字清除本应用数据库 - * @param context - * @param dbName - */ - public static void cleanDatabaseByName(Context context, String dbName) { - context.deleteDatabase(dbName); - } + /** + * 清除/data/data/com.xxx.xxx/files下的内容 + * + * @param context + */ + public static void cleanFiles(Context context) { + deleteFilesByDirectory(context.getFilesDir()); + } - /** - * 清除/data/data/com.xxx.xxx/files下的内容 - * @param context - */ - public static void cleanFiles(Context context) { - deleteFilesByDirectory(context.getFilesDir()); - } + /** + * 清除外部cache下的内容(/mnt/sdcard/android/data/com.xxx.xxx/cache) + * + * @param context + */ + public static void cleanExternalCache(Context context) { + if (Environment.getExternalStorageState().equals( + Environment.MEDIA_MOUNTED)) { + deleteFilesByDirectory(context.getExternalCacheDir()); + } + } - /** - * 清除外部cache下的内容(/mnt/sdcard/android/data/com.xxx.xxx/cache) - * @param context - */ - public static void cleanExternalCache(Context context) { - if (Environment.getExternalStorageState().equals( - Environment.MEDIA_MOUNTED)) { - deleteFilesByDirectory(context.getExternalCacheDir()); - } - } + /** + * 清除自定义路径下的文件,使用需小心,请不要误删。而且只支持目录下的文件删除 + * + * @param filePath + */ + public static void cleanCustomCache(String filePath) { + deleteFilesByDirectory(new File(filePath)); + } - /** - * 清除自定义路径下的文件,使用需小心,请不要误删。而且只支持目录下的文件删除 - * @param filePath - */ - public static void cleanCustomCache(String filePath) { - deleteFilesByDirectory(new File(filePath)); - } - - /** - * 清除自定义路径下的文件,使用需小心,请不要误删。而且只支持目录下的文件删除 - * @param filePath - */ - public static void cleanCustomCache(File file) { - deleteFilesByDirectory(file); - } + /** + * 清除自定义路径下的文件,使用需小心,请不要误删。而且只支持目录下的文件删除 + * + * @param filePath + */ + public static void cleanCustomCache(File file) { + deleteFilesByDirectory(file); + } - /** - * 清除本应用所有的数据 - * @param context - * @param filepath - */ - public static void cleanApplicationData(Context context, String... filepath) { - cleanInternalCache(context); - cleanExternalCache(context); - cleanDatabases(context); - cleanSharedPreference(context); - cleanFiles(context); - for (String filePath : filepath) { - cleanCustomCache(filePath); - } - } + /** + * 清除本应用所有的数据 + * + * @param context + * @param filepath + */ + public static void cleanApplicationData(Context context, String... filepath) { + cleanInternalCache(context); + cleanExternalCache(context); + cleanDatabases(context); + cleanSharedPreference(context); + cleanFiles(context); + for (String filePath : filepath) { + cleanCustomCache(filePath); + } + } - /** - * 删除方法 这里只会删除某个文件夹下的文件,如果传入的directory是个文件,将不做处理 - * @param directory - */ - private static void deleteFilesByDirectory(File directory) { - if (directory != null && directory.exists() && directory.isDirectory()) { - for (File child : directory.listFiles()) { - if (child.isDirectory()) { - deleteFilesByDirectory(child); - } - child.delete(); - } - } - } + /** + * 删除方法 这里只会删除某个文件夹下的文件,如果传入的directory是个文件,将不做处理 + * + * @param directory + */ + private static void deleteFilesByDirectory(File directory) { + if (directory != null && directory.exists() && directory.isDirectory()) { + for (File child : directory.listFiles()) { + if (child.isDirectory()) { + deleteFilesByDirectory(child); + } + child.delete(); + } + } + } } diff --git a/app/src/main/java/net/oschina/app/cache/DiskLruCache.java b/app/src/main/java/net/oschina/app/cache/DiskLruCache.java index e36f68359cc90007a4a7e5de0c9abc978f0f925d..1166948d6c68b38e0c30c0551365c570b8e9c71b 100644 --- a/app/src/main/java/net/oschina/app/cache/DiskLruCache.java +++ b/app/src/main/java/net/oschina/app/cache/DiskLruCache.java @@ -48,49 +48,49 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** - ****************************************************************************** + * ***************************************************************************** * Taken from the JB source code, can be found in: * libcore/luni/src/main/java/libcore/io/DiskLruCache.java * or direct link: * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java - ****************************************************************************** - * + * ***************************************************************************** + *

    * A cache that uses a bounded amount of space on a filesystem. Each cache * entry has a string key and a fixed number of values. Values are byte * sequences, accessible as streams or files. Each value must be between {@code * 0} and {@code Integer.MAX_VALUE} bytes in length. - * + *

    *

    The cache stores its data in a directory on the filesystem. This * directory must be exclusive to the cache; the cache may delete or overwrite * files from its directory. It is an error for multiple processes to use the * same cache directory at the same time. - * + *

    *

    This cache limits the number of bytes that it will store on the * filesystem. When the number of stored bytes exceeds the limit, the cache will * remove entries in the background until the limit is satisfied. The limit is * not strict: the cache may temporarily exceed it while waiting for files to be * deleted. The limit does not include filesystem overhead or the cache * journal so space-sensitive applications should set a conservative limit. - * + *

    *

    Clients call {@link #edit} to create or update the values of an entry. An * entry may have only one editor at one time; if a value is not available to be * edited then {@link #edit} will return null. *

      - *
    • When an entry is being created it is necessary to - * supply a full set of values; the empty value should be used as a - * placeholder if necessary. - *
    • When an entry is being edited, it is not necessary - * to supply data for every value; values default to their previous - * value. + *
    • When an entry is being created it is necessary to + * supply a full set of values; the empty value should be used as a + * placeholder if necessary. + *
    • When an entry is being edited, it is not necessary + * to supply data for every value; values default to their previous + * value. *
    * Every {@link #edit} call must be matched by a call to {@link Editor#commit} * or {@link Editor#abort}. Committing is atomic: a read observes the full set * of values as they were before or after the commit, but never a mix of values. - * + *

    *

    Clients call {@link #get} to read a snapshot of an entry. The read will * observe the value at the time that {@link #get} was called. Updates and * removals after the call do not impact ongoing reads. - * + *

    *

    This class is tolerant of some I/O errors. If files are missing from the * filesystem, the corresponding entries will be dropped from the cache. If * an error occurs while writing a cache value, the edit will fail silently. @@ -210,7 +210,7 @@ public final class DiskLruCache implements Closeable { * "\n". * * @throws java.io.EOFException if the stream is exhausted before the next newline - * character. + * character. */ public static String readAsciiLine(InputStream in) throws IOException { // TODO: support UTF-8 here instead @@ -266,11 +266,14 @@ public final class DiskLruCache implements Closeable { } } - /** This cache uses a single background thread to evict entries. */ + /** + * This cache uses a single background thread to evict entries. + */ private final ExecutorService executorService = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue()); private final Callable cleanupCallable = new Callable() { - @Override public Void call() throws Exception { + @Override + public Void call() throws Exception { synchronized (DiskLruCache.this) { if (journalWriter == null) { return null; // closed @@ -298,10 +301,10 @@ public final class DiskLruCache implements Closeable { * Opens the cache in {@code directory}, creating a cache if none exists * there. * - * @param directory a writable directory + * @param directory a writable directory * @param appVersion * @param valueCount the number of values per cache entry. Must be positive. - * @param maxSize the maximum number of bytes this cache should use to store + * @param maxSize the maximum number of bytes this cache should use to store * @throws java.io.IOException if reading or writing the cache directory fails */ public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) @@ -761,7 +764,8 @@ public final class DiskLruCache implements Closeable { return inputStreamToString(getInputStream(index)); } - @Override public void close() { + @Override + public void close() { for (InputStream in : ins) { closeQuietly(in); } @@ -859,7 +863,8 @@ public final class DiskLruCache implements Closeable { super(out); } - @Override public void write(int oneByte) { + @Override + public void write(int oneByte) { try { out.write(oneByte); } catch (IOException e) { @@ -867,7 +872,8 @@ public final class DiskLruCache implements Closeable { } } - @Override public void write(byte[] buffer, int offset, int length) { + @Override + public void write(byte[] buffer, int offset, int length) { try { out.write(buffer, offset, length); } catch (IOException e) { @@ -875,7 +881,8 @@ public final class DiskLruCache implements Closeable { } } - @Override public void close() { + @Override + public void close() { try { out.close(); } catch (IOException e) { @@ -883,7 +890,8 @@ public final class DiskLruCache implements Closeable { } } - @Override public void flush() { + @Override + public void flush() { try { out.flush(); } catch (IOException e) { @@ -896,16 +904,24 @@ public final class DiskLruCache implements Closeable { private final class Entry { private final String key; - /** Lengths of this entry's files. */ + /** + * Lengths of this entry's files. + */ private final long[] lengths; - /** True if this entry has ever been published */ + /** + * True if this entry has ever been published + */ private boolean readable; - /** The ongoing edit or null if this entry is not being edited. */ + /** + * The ongoing edit or null if this entry is not being edited. + */ private Editor currentEditor; - /** The sequence number of the most recently committed edit to this entry. */ + /** + * The sequence number of the most recently committed edit to this entry. + */ private long sequenceNumber; private Entry(String key) { diff --git a/app/src/main/java/net/oschina/app/cache/DiskLruCacheUtil.java b/app/src/main/java/net/oschina/app/cache/DiskLruCacheUtil.java index a02acff76303b5919dc1ced54f46fdf4763107ec..c9756589e6b8f145a71c6198736302b54c9387e9 100644 --- a/app/src/main/java/net/oschina/app/cache/DiskLruCacheUtil.java +++ b/app/src/main/java/net/oschina/app/cache/DiskLruCacheUtil.java @@ -1,5 +1,11 @@ package net.oschina.app.cache; +import android.content.Context; +import android.os.Environment; + +import net.oschina.app.util.TDevice; +import net.oschina.common.utils.StreamUtil; + import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; @@ -8,19 +14,11 @@ import java.io.Serializable; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import net.oschina.app.util.TDevice; - -import org.kymjs.kjframe.utils.FileUtils; - -import android.content.Context; -import android.os.Environment; - /** * 缓存工具类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @version 创建时间:2014年12月26日 下午4:53:13 - * */ public class DiskLruCacheUtil { @@ -35,7 +33,7 @@ public class DiskLruCacheUtil { /** * 保存对象缓存 - * + * * @param context * @param ser * @param key @@ -55,13 +53,13 @@ public class DiskLruCacheUtil { e.printStackTrace(); } finally { - FileUtils.closeIO(oos); + StreamUtil.close(oos); } } /** * 读取对象缓存 - * + * * @param context * @param key * @return @@ -78,14 +76,14 @@ public class DiskLruCacheUtil { } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { - FileUtils.closeIO(ois); + StreamUtil.close(ois); } return null; } /** * 获取DiskLruCache的editor - * + * * @param context * @param key * @return @@ -102,7 +100,7 @@ public class DiskLruCacheUtil { /** * 获取相应的缓存目录 - * + * * @param context * @param uniqueName * @return @@ -121,7 +119,7 @@ public class DiskLruCacheUtil { /** * 传入缓存的key值,以得到相应的MD5值 - * + * * @param key * @return */ diff --git a/app/src/main/java/net/oschina/app/db/DatabaseHelper.java b/app/src/main/java/net/oschina/app/db/DatabaseHelper.java index 469cb0340926c3b4f8f9afe6084de936f81923e7..5d837d7d74cb1d06d7bfd4aa402212fbb0c39ed4 100644 --- a/app/src/main/java/net/oschina/app/db/DatabaseHelper.java +++ b/app/src/main/java/net/oschina/app/db/DatabaseHelper.java @@ -6,11 +6,10 @@ import android.database.sqlite.SQLiteOpenHelper; /** * 创建便签的数据库 - * + * * @author kymjs - * + *

    * update:2014-01-12 updateor: fireant 内容:修改为全应用数据库 - * */ public class DatabaseHelper extends SQLiteOpenHelper { @@ -40,6 +39,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { } @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {} + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + } } \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/db/NoteDatabase.java b/app/src/main/java/net/oschina/app/db/NoteDatabase.java index d7f957283efa96e3a8681beb6ddbe42e752fb216..015f52f4b2d0e07d12806612666d8cc5ee6bf697 100644 --- a/app/src/main/java/net/oschina/app/db/NoteDatabase.java +++ b/app/src/main/java/net/oschina/app/db/NoteDatabase.java @@ -1,13 +1,14 @@ package net.oschina.app.db; -import java.util.ArrayList; -import java.util.List; - -import net.oschina.app.bean.NotebookData; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import net.oschina.app.bean.NotebookData; + +import java.util.ArrayList; +import java.util.List; + public class NoteDatabase { private final DatabaseHelper dbHelper; @@ -18,7 +19,7 @@ public class NoteDatabase { /** * 增 - * + * * @param data */ public void insert(NotebookData data) { @@ -27,36 +28,36 @@ public class NoteDatabase { sql += "(_id, iid, time, date, content, color) values(?, ?, ?, ?, ?, ?)"; SQLiteDatabase sqlite = dbHelper.getWritableDatabase(); - sqlite.execSQL(sql, new String[] { data.getId() + "", + sqlite.execSQL(sql, new String[]{data.getId() + "", data.getIid() + "", data.getUnixTime() + "", data.getDate(), - data.getContent(), data.getColor() + "" }); + data.getContent(), data.getColor() + ""}); sqlite.close(); } /** * 删 - * + * * @param id */ public void delete(int id) { SQLiteDatabase sqlite = dbHelper.getWritableDatabase(); String sql = ("delete from " + DatabaseHelper.NOTE_TABLE_NAME + " where _id=?"); - sqlite.execSQL(sql, new Integer[] { id }); + sqlite.execSQL(sql, new Integer[]{id}); sqlite.close(); } /** * 改 - * + * * @param data */ public void update(NotebookData data) { SQLiteDatabase sqlite = dbHelper.getWritableDatabase(); String sql = ("update " + DatabaseHelper.NOTE_TABLE_NAME + " set iid=?, time=?, date=?, content=?, color=? where _id=?"); sqlite.execSQL(sql, - new String[] { data.getIid() + "", data.getUnixTime() + "", + new String[]{data.getIid() + "", data.getUnixTime() + "", data.getDate(), data.getContent(), - data.getColor() + "", data.getId() + "" }); + data.getColor() + "", data.getId() + ""}); sqlite.close(); } @@ -66,7 +67,7 @@ public class NoteDatabase { /** * 查 - * + * * @param where * @return */ @@ -96,7 +97,7 @@ public class NoteDatabase { /** * 重置 - * + * * @param datas */ public void reset(List datas) { @@ -114,7 +115,7 @@ public class NoteDatabase { /** * 保存一条数据到本地(若已存在则直接覆盖) - * + * * @param data */ public void save(NotebookData data) { diff --git a/app/src/main/java/net/oschina/app/emoji/DisplayRules.java b/app/src/main/java/net/oschina/app/emoji/DisplayRules.java index 90440197b613407cba0184d9483507c2febdd5f1..d0c26a6aebca3569b79ff8003b3ae8028a6130e7 100644 --- a/app/src/main/java/net/oschina/app/emoji/DisplayRules.java +++ b/app/src/main/java/net/oschina/app/emoji/DisplayRules.java @@ -15,800 +15,441 @@ */ package net.oschina.app.emoji; +import net.oschina.app.R; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import net.oschina.app.R; - /** * Emoji在手机上的显示规则 - * + * * @author kymjs (http://www.kymjs.com) */ public enum DisplayRules { // 注意:value不能从0开始,因为0会被库自动设置为删除按钮 // int type, int value, int resId, String cls - KJEMOJI0(0, 1, R.drawable.smiley_0, "[微笑]", "[0]"), KJEMOJI1(0, 1, - R.drawable.smiley_1, "[撇嘴]", "[1]"), KJEMOJI2(0, 1, - R.drawable.smiley_2, "[色]", "[2]"), KJEMOJI3(0, 1, - R.drawable.smiley_3, "[发呆]", "[3]"), KJEMOJI4(0, 1, - R.drawable.smiley_4, "[得意]", "[4]"), KJEMOJI5(0, 1, - R.drawable.smiley_5, "[流泪]", "[5]"), KJEMOJI6(0, 1, - R.drawable.smiley_6, "[害羞]", "[6]"), KJEMOJI7(0, 1, - R.drawable.smiley_7, "[闭嘴]", "[7]"), KJEMOJI8(0, 1, - R.drawable.smiley_8, "[睡]", "[8]"), KJEMOJI9(0, 1, - R.drawable.smiley_9, "[大哭]", "[9]"), KJEMOJI10(0, 1, - R.drawable.smiley_10, "[尴尬]", "[10]"), KJEMOJI11(0, 1, - R.drawable.smiley_11, "[发怒]", "[11]"), KJEMOJI12(0, 1, - R.drawable.smiley_12, "调皮", "[12]"), KJEMOJI13(0, 1, - R.drawable.smiley_13, "[呲牙]", "[13]"), KJEMOJI14(0, 1, - R.drawable.smiley_14, "[惊讶]", "[14]"), KJEMOJI15(0, 1, - R.drawable.smiley_15, "[难过]", "[15]"), KJEMOJI16(0, 1, - R.drawable.smiley_16, "[酷]", "[16]"), KJEMOJI17(0, 1, - R.drawable.smiley_17, "[冷汗]", "[17]"), KJEMOJI18(0, 1, - R.drawable.smiley_18, "[抓狂]", "[18]"), KJEMOJI19(0, 1, - R.drawable.smiley_19, "[吐]", "[19]"), KJEMOJI20(0, 1, - R.drawable.smiley_20, "[偷笑]", "[20]"), KJEMOJI21(0, 1, - R.drawable.smiley_21, "[可爱]", "[21]"), KJEMOJI22(0, 1, - R.drawable.smiley_22, "[白眼]", "[22]"), KJEMOJI23(0, 1, - R.drawable.smiley_23, "[傲慢]", "[23]"), KJEMOJI24(0, 1, - R.drawable.smiley_24, "[饥饿]", "[24]"), KJEMOJI25(0, 1, - R.drawable.smiley_25, "[困]", "[25]"), KJEMOJI26(0, 1, - R.drawable.smiley_26, "[惊恐]", "[26]"), KJEMOJI27(0, 1, - R.drawable.smiley_27, "[流汗]", "[27]"), KJEMOJI28(0, 1, - R.drawable.smiley_28, "[憨笑]", "[28]"), KJEMOJI29(0, 1, - R.drawable.smiley_29, "[大兵]", "[29]"), KJEMOJI30(0, 1, - R.drawable.smiley_30, "[奋斗]", "[30]"), KJEMOJI31(0, 1, - R.drawable.smiley_31, "[咒骂]", "[31]"), KJEMOJI32(0, 1, - R.drawable.smiley_32, "[疑问]", "[32]"), KJEMOJI33(0, 1, - R.drawable.smiley_33, "[嘘]", "[33]"), KJEMOJI34(0, 1, - R.drawable.smiley_34, "[晕]", "[34]"), KJEMOJI35(0, 1, - R.drawable.smiley_35, "折磨", "[35]"), KJEMOJI36(0, 1, - R.drawable.smiley_36, "衰", "[36]"), KJEMOJI37(0, 1, - R.drawable.smiley_37, "骷髅", "[37]"), KJEMOJI38(0, 1, - R.drawable.smiley_38, "敲打", "[38]"), KJEMOJI39(0, 1, - R.drawable.smiley_39, "再见", "[39]"), KJEMOJI40(0, 1, - R.drawable.smiley_40, "擦汗", "[40]"), KJEMOJI41(0, 1, - R.drawable.smiley_41, "抠鼻", "[41]"), KJEMOJI42(0, 1, - R.drawable.smiley_42, "鼓掌", "[42]"), KJEMOJI43(0, 1, - R.drawable.smiley_43, "糗大了", "[43]"), KJEMOJI44(0, 1, - R.drawable.smiley_44, "坏笑", "[44]"), KJEMOJI45(0, 1, - R.drawable.smiley_45, "[左哼哼]", "[45]"), KJEMOJI46(0, 1, - R.drawable.smiley_46, "[右哼哼]", "[46]"), KJEMOJI47(0, 1, - R.drawable.smiley_47, "[哈欠]", "[47]"), KJEMOJI48(0, 1, - R.drawable.smiley_48, "[鄙视]", "[48]"), KJEMOJI49(0, 1, - R.drawable.smiley_49, "[委屈]", "[49]"), KJEMOJI50(0, 1, - R.drawable.smiley_50, "[快哭了]", "[50]"), KJEMOJI51(0, 1, - R.drawable.smiley_51, "[阴险]", "[51]"), KJEMOJI52(0, 1, - R.drawable.smiley_52, "[亲亲]", "[52]"), KJEMOJI53(0, 1, - R.drawable.smiley_53, "[吓]", "[53]"), KJEMOJI54(0, 1, - R.drawable.smiley_54, "[可怜]", "[54]"), KJEMOJI55(0, 1, - R.drawable.smiley_55, "[菜刀]", "[55]"), KJEMOJI56(0, 1, - R.drawable.smiley_56, "[西瓜]", "[56]"), KJEMOJI57(0, 1, - R.drawable.smiley_57, "[啤酒]", "[57]"), KJEMOJI58(0, 1, - R.drawable.smiley_58, "[篮球]", "[58]"), KJEMOJI59(0, 1, - R.drawable.smiley_59, "[乒乓]", "[59]"), KJEMOJI60(0, 1, - R.drawable.smiley_60, "[咖啡]", "[60]"), KJEMOJI61(0, 1, - R.drawable.smiley_61, "[饭]", "[61]"), KJEMOJI62(0, 1, - R.drawable.smiley_62, "[猪头]", "[62]"), KJEMOJI63(0, 1, - R.drawable.smiley_63, "[玫瑰]", "[63]"), KJEMOJI64(0, 1, - R.drawable.smiley_64, "[凋谢]", "[64]"), KJEMOJI65(0, 1, - R.drawable.smiley_65, "[嘴唇]", "[65]"), KJEMOJI66(0, 1, - R.drawable.smiley_66, "[爱心]", "[66]"), KJEMOJI67(0, 1, - R.drawable.smiley_67, "[心碎]", "[67]"), KJEMOJI68(0, 1, - R.drawable.smiley_68, "[蛋糕]", "[68]"), KJEMOJI69(0, 1, - R.drawable.smiley_69, "[闪电]", "[69]"), KJEMOJI70(0, 1, - R.drawable.smiley_70, "[炸弹]", "[70]"), KJEMOJI71(0, 1, - R.drawable.smiley_71, "[刀]", "[71]"), KJEMOJI72(0, 1, - R.drawable.smiley_72, "[足球]", "[72]"), KJEMOJI73(0, 1, - R.drawable.smiley_73, "[瓢虫]", "[73]"), KJEMOJI74(0, 1, - R.drawable.smiley_74, "[便便]", "[74]"), KJEMOJI75(0, 1, - R.drawable.smiley_75, "[月亮]", "[75]"), KJEMOJI76(0, 1, - R.drawable.smiley_76, "[太阳]", "[76]"), KJEMOJI77(0, 1, - R.drawable.smiley_77, "[礼物]", "[77]"), KJEMOJI78(0, 1, - R.drawable.smiley_78, "[拥抱]", "[78]"), KJEMOJI79(0, 1, - R.drawable.smiley_79, "[强]", "[79]"), KJEMOJI80(0, 1, - R.drawable.smiley_80, "[弱]", "[80]"), KJEMOJI81(0, 1, - R.drawable.smiley_81, "[握手]", "[81]"), KJEMOJI82(0, 1, - R.drawable.smiley_82, "[胜利]", "[82]"), KJEMOJI83(0, 1, - R.drawable.smiley_83, "[抱拳]", "[83]"), KJEMOJI84(0, 1, - R.drawable.smiley_84, "[勾引]", "[84]"), KJEMOJI85(0, 1, - R.drawable.smiley_85, "[拳头]", "[85]"), KJEMOJI86(0, 1, - R.drawable.smiley_86, "[差劲]", "[86]"), KJEMOJI87(0, 1, - R.drawable.smiley_87, "[爱你]", "[87]"), KJEMOJI88(0, 1, - R.drawable.smiley_88, "[NO]", "[88]"), KJEMOJI89(0, 1, - R.drawable.smiley_89, "[OK]", "[89]"), KJEMOJI90(0, 1, - R.drawable.smiley_90, "[爱情]", "[90]"), KJEMOJI91(0, 1, - R.drawable.smiley_91, "[飞吻]", "[91]"), KJEMOJI92(0, 1, - R.drawable.smiley_92, "[跳跳]", "[92]"), KJEMOJI93(0, 1, - R.drawable.smiley_93, "[发抖]", "[93]"), KJEMOJI94(0, 1, - R.drawable.smiley_94, "[怄火]", "[94]"), KJEMOJI95(0, 1, - R.drawable.smiley_95, "[转圈]", "[95]"), KJEMOJI96(0, 1, - R.drawable.smiley_96, "[磕头]", "[96]"), KJEMOJI97(0, 1, - R.drawable.smiley_97, "[回头]", "[97]"), KJEMOJI98(0, 1, - R.drawable.smiley_98, "[跳绳]", "[98]"), KJEMOJI99(0, 1, - R.drawable.smiley_99, "[投降]", "[99]"), KJEMOJI100(0, 1, - R.drawable.smiley_100, "[激动]", "[100]"), KJEMOJI101(0, 1, - R.drawable.smiley_101, "[乱舞]", "[101]"), KJEMOJI102(0, 1, - R.drawable.smiley_102, "[献吻]", "[102]"), KJEMOJI103(0, 1, - R.drawable.smiley_103, "[左太极]", "[103]"), KJEMOJI104(0, 1, - R.drawable.smiley_104, "[右太极]", "[104]"), - - GITHUB0(1, 1, R.drawable.bowtie, ":bowtie:", ":bowtie:"), - - GITHUB1(1, 1, R.drawable.smile, ":smile:", ":smile:"), - - GITHUB2(1, 1, R.drawable.laughing, ":laughing:", ":laughing:"), - - GITHUB3(1, 1, R.drawable.blush, ":blush:", ":blush:"), - - GITHUB4(1, 1, R.drawable.smiley, ":smiley:", ":smiley:"), - - GITHUB5(1, 1, R.drawable.relaxed, ":relaxed:", ":relaxed:"), - - GITHUB6(1, 1, R.drawable.smirk, ":smirk:", ":smirk:"), - - GITHUB7(1, 1, R.drawable.heart_eyes, ":heart_eyes:", ":heart_eyes:"), - - GITHUB8(1, 1, R.drawable.kissing_heart, ":kissing_heart:", - ":kissing_heart:"), - - GITHUB9(1, 1, R.drawable.kissing_closed_eyes, ":kissing_closed_eyes:", - ":kissing_closed_eyes:"), - - GITHUB10(1, 1, R.drawable.flushed, ":flushed:", ":flushed:"), - - GITHUB11(1, 1, R.drawable.relieved, ":relieved:", ":relieved:"), - - GITHUB12(1, 1, R.drawable.satisfied, ":satisfied:", ":satisfied:"), - - GITHUB13(1, 1, R.drawable.grin, ":grin:", ":grin:"), - - GITHUB14(1, 1, R.drawable.wink, ":wink:", ":wink:"), - - GITHUB15(1, 1, R.drawable.stuck_out_tongue_winking_eye, - ":stuck_out_tongue_winking_eye:", ":stuck_out_tongue_winking_eye:"), - - GITHUB16(1, 1, R.drawable.stuck_out_tongue_closed_eyes, - ":stuck_out_tongue_closed_eyes:", ":stuck_out_tongue_closed_eyes:"), - - GITHUB17(1, 1, R.drawable.grinning, ":grinning:", ":grinning:"), - - GITHUB18(1, 1, R.drawable.kissing, ":kissing:", ":kissing:"), - - GITHUB19(1, 1, R.drawable.kissing_smiling_eyes, ":kissing_smiling_eyes:", - ":kissing_smiling_eyes:"), - - GITHUB20(1, 1, R.drawable.stuck_out_tongue, ":stuck_out_tongue:", - ":stuck_out_tongue:"), - - GITHUB21(1, 1, R.drawable.sleeping, ":sleeping:", ":sleeping:"), - - GITHUB22(1, 1, R.drawable.worried, ":worried:", ":worried:"), - - GITHUB23(1, 1, R.drawable.frowning, ":frowning:", ":frowning:"), - - GITHUB24(1, 1, R.drawable.anguished, ":anguished:", ":anguished:"), - - GITHUB25(1, 1, R.drawable.open_mouth, ":open_mouth:", ":open_mouth:"), - - GITHUB26(1, 1, R.drawable.grimacing, ":grimacing:", ":grimacing:"), - - GITHUB27(1, 1, R.drawable.confused, ":confused:", ":confused:"), - - GITHUB28(1, 1, R.drawable.hushed, ":hushed:", ":hushed:"), - - GITHUB29(1, 1, R.drawable.expressionless, ":expressionless:", - ":expressionless:"), - - GITHUB30(1, 1, R.drawable.unamused, ":unamused:", ":unamused:"), - - GITHUB31(1, 1, R.drawable.sweat_smile, ":sweat_smile:", ":sweat_smile:"), - - GITHUB32(1, 1, R.drawable.sweat, ":sweat:", ":sweat:"), - - GITHUB33(1, 1, R.drawable.disappointed_relieved, ":disappointed_relieved:", - ":disappointed_relieved:"), - - GITHUB34(1, 1, R.drawable.weary, ":weary:", ":weary:"), - - GITHUB35(1, 1, R.drawable.pensive, ":pensive:", ":pensive:"), - - GITHUB36(1, 1, R.drawable.disappointed, ":disappointed:", ":disappointed:"), - - GITHUB37(1, 1, R.drawable.confounded, ":confounded:", ":confounded:"), - - GITHUB38(1, 1, R.drawable.fearful, ":fearful:", ":fearful:"), - - GITHUB39(1, 1, R.drawable.cold_sweat, ":cold_sweat:", ":cold_sweat:"), - - GITHUB40(1, 1, R.drawable.persevere, ":persevere:", ":persevere:"), - - GITHUB41(1, 1, R.drawable.cry, ":cry:", ":cry:"), - - GITHUB42(1, 1, R.drawable.sob, ":sob:", ":sob:"), - - GITHUB43(1, 1, R.drawable.joy, ":joy:", ":joy:"), - - GITHUB44(1, 1, R.drawable.astonished, ":astonished:", ":astonished:"), - - GITHUB45(1, 1, R.drawable.scream, ":scream:", ":scream:"), - - GITHUB46(1, 1, R.drawable.neckbeard, ":neckbeard:", ":neckbeard:"), - - GITHUB47(1, 1, R.drawable.tired_face, ":tired_face:", ":tired_face:"), - - GITHUB48(1, 1, R.drawable.angry, ":angry:", ":angry:"), - - GITHUB49(1, 1, R.drawable.rage, ":rage:", ":rage:"), - - GITHUB50(1, 1, R.drawable.triumph, ":triumph:", ":triumph:"), - - GITHUB51(1, 1, R.drawable.sleepy, ":sleepy:", ":sleepy:"), - - GITHUB52(1, 1, R.drawable.yum, ":yum:", ":yum:"), - - GITHUB53(1, 1, R.drawable.mask, ":mask:", ":mask:"), - - GITHUB54(1, 1, R.drawable.sunglasses, ":sunglasses:", ":sunglasses:"), - - GITHUB55(1, 1, R.drawable.dizzy_face, ":dizzy_face:", ":dizzy_face:"), - - GITHUB56(1, 1, R.drawable.imp, ":imp:", ":imp:"), - - GITHUB57(1, 1, R.drawable.smiling_imp, ":smiling_imp:", ":smiling_imp:"), - - GITHUB58(1, 1, R.drawable.neutral_face, ":neutral_face:", ":neutral_face:"), - - GITHUB59(1, 1, R.drawable.no_mouth, ":no_mouth:", ":no_mouth:"), - - GITHUB60(1, 1, R.drawable.innocent, ":innocent:", ":innocent:"), - - GITHUB61(1, 1, R.drawable.alien, ":alien:", ":alien:"), - - GITHUB62(1, 1, R.drawable.yellow_heart, ":yellow_heart:", ":yellow_heart:"), - - GITHUB63(1, 1, R.drawable.blue_heart, ":blue_heart:", ":blue_heart:"), - - GITHUB64(1, 1, R.drawable.purple_heart, ":purple_heart:", ":purple_heart:"), - - GITHUB65(1, 1, R.drawable.heart, ":heart:", ":heart:"), - - GITHUB66(1, 1, R.drawable.green_heart, ":green_heart:", ":green_heart:"), - - GITHUB67(1, 1, R.drawable.broken_heart, ":broken_heart:", ":broken_heart:"), - - GITHUB68(1, 1, R.drawable.heartbeat, ":heartbeat:", ":heartbeat:"), - - GITHUB69(1, 1, R.drawable.heartpulse, ":heartpulse:", ":heartpulse:"), - - GITHUB70(1, 1, R.drawable.two_hearts, ":two_hearts:", ":two_hearts:"), - - GITHUB71(1, 1, R.drawable.revolving_hearts, ":revolving_hearts:", - ":revolving_hearts:"), - - GITHUB72(1, 1, R.drawable.cupid, ":cupid:", ":cupid:"), - - GITHUB73(1, 1, R.drawable.sparkling_heart, ":sparkling_heart:", - ":sparkling_heart:"), - - GITHUB74(1, 1, R.drawable.sparkles, ":sparkles:", ":sparkles:"), - - GITHUB75(1, 1, R.drawable.star, ":star:", ":star:"), - - GITHUB76(1, 1, R.drawable.star2, ":star2:", ":star2:"), - - GITHUB77(1, 1, R.drawable.dizzy, ":dizzy:", ":dizzy:"), - - GITHUB78(1, 1, R.drawable.boom, ":boom:", ":boom:"), - - GITHUB79(1, 1, R.drawable.collision, ":collision:", ":collision:"), - - GITHUB80(1, 1, R.drawable.anger, ":anger:", ":anger:"), - - GITHUB81(1, 1, R.drawable.exclamation, ":exclamation:", ":exclamation:"), - - GITHUB82(1, 1, R.drawable.question, ":question:", ":question:"), - - GITHUB83(1, 1, R.drawable.grey_exclamation, ":grey_exclamation:", - ":grey_exclamation:"), - - GITHUB84(1, 1, R.drawable.grey_question, ":grey_question:", - ":grey_question:"), - - GITHUB85(1, 1, R.drawable.zzz, ":zzz:", ":zzz:"), - - GITHUB86(1, 1, R.drawable.dash, ":dash:", ":dash:"), - - GITHUB87(1, 1, R.drawable.sweat_drops, ":sweat_drops:", ":sweat_drops:"), - - GITHUB88(1, 1, R.drawable.notes, ":notes:", ":notes:"), - - GITHUB89(1, 1, R.drawable.musical_note, ":musical_note:", ":musical_note:"), - - GITHUB90(1, 1, R.drawable.fire, ":fire:", ":fire:"), - - GITHUB91(1, 1, R.drawable.hankey, ":hankey:", ":hankey:"), - - GITHUB92(1, 1, R.drawable.poop, ":poop:", ":poop:"), - - GITHUB93(1, 1, R.drawable.shit, ":shit:", ":shit:"), - - GITHUB94(1, 1, R.drawable.thumbsup, ":+1:", ":+1:"), - - GITHUB95(1, 1, R.drawable.thumbsup, ":thumbsup:", ":thumbsup:"), - - GITHUB96(1, 1, R.drawable.the_1, ":-1:", ":-1:"), - - GITHUB97(1, 1, R.drawable.thumbsdown, ":thumbsdown:", ":thumbsdown:"), - - GITHUB98(1, 1, R.drawable.ok_hand, ":ok_hand:", ":ok_hand:"), - - GITHUB99(1, 1, R.drawable.punch, ":punch:", ":punch:"), - - GITHUB100(1, 1, R.drawable.facepunch, ":facepunch:", ":facepunch:"), - - GITHUB101(1, 1, R.drawable.fist, ":fist:", ":fist:"), - - GITHUB102(1, 1, R.drawable.v, ":v:", ":v:"), - - GITHUB103(1, 1, R.drawable.wave, ":wave:", ":wave:"), - - GITHUB104(1, 1, R.drawable.hand, ":hand:", ":hand:"), - - GITHUB105(1, 1, R.drawable.raised_hand, ":raised_hand:", ":raised_hand:"), - - GITHUB106(1, 1, R.drawable.open_hands, ":open_hands:", ":open_hands:"), - - GITHUB107(1, 1, R.drawable.point_up, ":point_up:", ":point_up:"), - - GITHUB108(1, 1, R.drawable.point_down, ":point_down:", ":point_down:"), - - GITHUB109(1, 1, R.drawable.point_left, ":point_left:", ":point_left:"), - - GITHUB110(1, 1, R.drawable.point_right, ":point_right:", ":point_right:"), - - GITHUB111(1, 1, R.drawable.raised_hands, ":raised_hands:", ":raised_hands:"), - - GITHUB112(1, 1, R.drawable.pray, ":pray:", ":pray:"), - - GITHUB113(1, 1, R.drawable.point_up_2, ":point_up_2:", ":point_up_2:"), - - GITHUB114(1, 1, R.drawable.clap, ":clap:", ":clap:"), - - GITHUB115(1, 1, R.drawable.muscle, ":muscle:", ":muscle:"), - - GITHUB116(1, 1, R.drawable.metal, ":metal:", ":metal:"), - - GITHUB117(1, 1, R.drawable.fu, ":fu:", ":fu:"), - - GITHUB118(1, 1, R.drawable.walking, ":walking:", ":walking:"), - - GITHUB119(1, 1, R.drawable.runner, ":runner:", ":runner:"), - - GITHUB120(1, 1, R.drawable.running, ":running:", ":running:"), - - GITHUB121(1, 1, R.drawable.couple, ":couple:", ":couple:"), - - GITHUB122(1, 1, R.drawable.family, ":family:", ":family:"), - - GITHUB123(1, 1, R.drawable.two_men_holding_hands, - ":two_men_holding_hands:", ":two_men_holding_hands:"), - - GITHUB124(1, 1, R.drawable.two_women_holding_hands, - ":two_women_holding_hands:", ":two_women_holding_hands:"), - - GITHUB125(1, 1, R.drawable.dancer, ":dancer:", ":dancer:"), - - GITHUB126(1, 1, R.drawable.dancers, ":dancers:", ":dancers:"), - - GITHUB127(1, 1, R.drawable.ok_woman, ":ok_woman:", ":ok_woman:"), - - GITHUB128(1, 1, R.drawable.no_good, ":no_good:", ":no_good:"), - - GITHUB129(1, 1, R.drawable.information_desk_person, - ":information_desk_person:", ":information_desk_person:"), - - GITHUB130(1, 1, R.drawable.raising_hand, ":raising_hand:", ":raising_hand:"), - - GITHUB131(1, 1, R.drawable.bride_with_veil, ":bride_with_veil:", - ":bride_with_veil:"), - - GITHUB132(1, 1, R.drawable.person_with_pouting_face, - ":person_with_pouting_face:", ":person_with_pouting_face:"), - - GITHUB133(1, 1, R.drawable.person_frowning, ":person_frowning:", - ":person_frowning:"), - - GITHUB134(1, 1, R.drawable.bow, ":bow:", ":bow:"), - - GITHUB135(1, 1, R.drawable.couplekiss, ":couplekiss:", ":couplekiss:"), - - GITHUB136(1, 1, R.drawable.couple_with_heart, ":couple_with_heart:", - ":couple_with_heart:"), - - GITHUB137(1, 1, R.drawable.massage, ":massage:", ":massage:"), - - GITHUB138(1, 1, R.drawable.haircut, ":haircut:", ":haircut:"), - - GITHUB139(1, 1, R.drawable.nail_care, ":nail_care:", ":nail_care:"), - - GITHUB140(1, 1, R.drawable.boy, ":boy:", ":boy:"), - - GITHUB141(1, 1, R.drawable.girl, ":girl:", ":girl:"), - - GITHUB142(1, 1, R.drawable.woman, ":woman:", ":woman:"), - - GITHUB143(1, 1, R.drawable.man, ":man:", ":man:"), - - GITHUB144(1, 1, R.drawable.baby, ":baby:", ":baby:"), - - GITHUB145(1, 1, R.drawable.older_woman, ":older_woman:", ":older_woman:"), - - GITHUB146(1, 1, R.drawable.older_man, ":older_man:", ":older_man:"), - - GITHUB147(1, 1, R.drawable.person_with_blond_hair, - ":person_with_blond_hair:", ":person_with_blond_hair:"), - - GITHUB148(1, 1, R.drawable.man_with_gua_pi_mao, ":man_with_gua_pi_mao:", - ":man_with_gua_pi_mao:"), - - GITHUB149(1, 1, R.drawable.man_with_turban, ":man_with_turban:", - ":man_with_turban:"), - - GITHUB150(1, 1, R.drawable.construction_worker, ":construction_worker:", - ":construction_worker:"), - - GITHUB151(1, 1, R.drawable.cop, ":cop:", ":cop:"), - - GITHUB152(1, 1, R.drawable.angel, ":angel:", ":angel:"), - - GITHUB153(1, 1, R.drawable.princess, ":princess:", ":princess:"), - - GITHUB154(1, 1, R.drawable.smiley_cat, ":smiley_cat:", ":smiley_cat:"), - - GITHUB155(1, 1, R.drawable.smile_cat, ":smile_cat:", ":smile_cat:"), - - GITHUB156(1, 1, R.drawable.heart_eyes_cat, ":heart_eyes_cat:", - ":heart_eyes_cat:"), - - GITHUB157(1, 1, R.drawable.kissing_cat, ":kissing_cat:", ":kissing_cat:"), - - GITHUB158(1, 1, R.drawable.smirk_cat, ":smirk_cat:", ":smirk_cat:"), - - GITHUB159(1, 1, R.drawable.scream_cat, ":scream_cat:", ":scream_cat:"), - - GITHUB160(1, 1, R.drawable.crying_cat_face, ":crying_cat_face:", - ":crying_cat_face:"), - - GITHUB161(1, 1, R.drawable.joy_cat, ":joy_cat:", ":joy_cat:"), - - GITHUB162(1, 1, R.drawable.pouting_cat, ":pouting_cat:", ":pouting_cat:"), - - GITHUB163(1, 1, R.drawable.japanese_ogre, ":japanese_ogre:", - ":japanese_ogre:"), - - GITHUB164(1, 1, R.drawable.japanese_goblin, ":japanese_goblin:", - ":japanese_goblin:"), - - GITHUB165(1, 1, R.drawable.see_no_evil, ":see_no_evil:", ":see_no_evil:"), - - GITHUB166(1, 1, R.drawable.hear_no_evil, ":hear_no_evil:", ":hear_no_evil:"), - - GITHUB167(1, 1, R.drawable.speak_no_evil, ":speak_no_evil:", - ":speak_no_evil:"), - - GITHUB168(1, 1, R.drawable.guardsman, ":guardsman:", ":guardsman:"), - - GITHUB169(1, 1, R.drawable.skull, ":skull:", ":skull:"), - - GITHUB170(1, 1, R.drawable.feet, ":feet:", ":feet:"), - - GITHUB171(1, 1, R.drawable.lips, ":lips:", ":lips:"), - - GITHUB172(1, 1, R.drawable.kiss, ":kiss:", ":kiss:"), - - GITHUB173(1, 1, R.drawable.droplet, ":droplet:", ":droplet:"), - - GITHUB174(1, 1, R.drawable.ear, ":ear:", ":ear:"), - - GITHUB175(1, 1, R.drawable.eyes, ":eyes:", ":eyes:"), - - GITHUB176(1, 1, R.drawable.nose, ":nose:", ":nose:"), - - GITHUB177(1, 1, R.drawable.tongue, ":tongue:", ":tongue:"), - - GITHUB178(1, 1, R.drawable.love_letter, ":love_letter:", ":love_letter:"), - - GITHUB179(1, 1, R.drawable.bust_in_silhouette, ":bust_in_silhouette:", - ":bust_in_silhouette:"), - - GITHUB180(1, 1, R.drawable.busts_in_silhouette, ":busts_in_silhouette:", - ":busts_in_silhouette:"), - - GITHUB181(1, 1, R.drawable.speech_balloon, ":speech_balloon:", - ":speech_balloon:"), - - GITHUB182(1, 1, R.drawable.thought_balloon, ":thought_balloon:", - ":thought_balloon:"), - - GITHUB183(1, 1, R.drawable.feelsgood, ":feelsgood:", ":feelsgood:"), - - GITHUB184(1, 1, R.drawable.finnadie, ":finnadie:", ":finnadie:"), - - GITHUB185(1, 1, R.drawable.goberserk, ":goberserk:", ":goberserk:"), - - GITHUB186(1, 1, R.drawable.godmode, ":godmode:", ":godmode:"), - - GITHUB187(1, 1, R.drawable.hurtrealbad, ":hurtrealbad:", ":hurtrealbad:"), - - GITHUB188(1, 1, R.drawable.rage1, ":rage1:", ":rage1:"), - - GITHUB189(1, 1, R.drawable.rage2, ":rage2:", ":rage2:"), - - GITHUB190(1, 1, R.drawable.rage3, ":rage3:", ":rage3:"), - - GITHUB191(1, 1, R.drawable.rage4, ":rage4:", ":rage4:"), - - GITHUB192(1, 1, R.drawable.suspect, ":suspect:", ":suspect:"), - - GITHUB193(1, 1, R.drawable.trollface, ":trollface:", ":trollface:"), - - Nature0(2, 1, R.drawable.sunny, ":sunny:", ":sunny:"), - - Nature1(2, 1, R.drawable.umbrella, ":umbrella:", ":umbrella:"), - - Nature2(2, 1, R.drawable.cloud, ":cloud:", ":cloud:"), - - Nature3(2, 1, R.drawable.snowflake, ":snowflake:", ":snowflake:"), - - Nature4(2, 1, R.drawable.snowman, ":snowman:", ":snowman:"), - - Nature5(2, 1, R.drawable.zap, ":zap:", ":zap:"), - - Nature6(2, 1, R.drawable.cyclone, ":cyclone:", ":cyclone:"), - - Nature7(2, 1, R.drawable.foggy, ":foggy:", ":foggy:"), - - Nature8(2, 1, R.drawable.ocean, ":ocean:", ":ocean:"), - - Nature9(2, 1, R.drawable.cat, ":cat:", ":cat:"), - - Nature10(2, 1, R.drawable.dog, ":dog:", ":dog:"), - - Nature11(2, 1, R.drawable.mouse, ":mouse:", ":mouse:"), - - Nature12(2, 1, R.drawable.hamster, ":hamster:", ":hamster:"), - - Nature13(2, 1, R.drawable.rabbit, ":rabbit:", ":rabbit:"), - - Nature14(2, 1, R.drawable.wolf, ":wolf:", ":wolf:"), - - Nature15(2, 1, R.drawable.frog, ":frog:", ":frog:"), - - Nature16(2, 1, R.drawable.tiger, ":tiger:", ":tiger:"), - - Nature17(2, 1, R.drawable.koala, ":koala:", ":koala:"), - - Nature18(2, 1, R.drawable.bear, ":bear:", ":bear:"), - - Nature19(2, 1, R.drawable.pig, ":pig:", ":pig:"), - - Nature20(2, 1, R.drawable.pig_nose, ":pig_nose:", ":pig_nose:"), - - Nature21(2, 1, R.drawable.cow, ":cow:", ":cow:"), - - Nature22(2, 1, R.drawable.boar, ":boar:", ":boar:"), - - Nature23(2, 1, R.drawable.monkey_face, ":monkey_face:", ":monkey_face:"), - - Nature24(2, 1, R.drawable.monkey, ":monkey:", ":monkey:"), - - Nature25(2, 1, R.drawable.horse, ":horse:", ":horse:"), - - Nature26(2, 1, R.drawable.racehorse, ":racehorse:", ":racehorse:"), - - Nature27(2, 1, R.drawable.camel, ":camel:", ":camel:"), - - Nature28(2, 1, R.drawable.sheep, ":sheep:", ":sheep:"), - - Nature29(2, 1, R.drawable.elephant, ":elephant:", ":elephant:"), - - Nature30(2, 1, R.drawable.panda_face, ":panda_face:", ":panda_face:"), - - Nature31(2, 1, R.drawable.snake, ":snake:", ":snake:"), - - Nature32(2, 1, R.drawable.bird, ":bird:", ":bird:"), - - Nature33(2, 1, R.drawable.baby_chick, ":baby_chick:", ":baby_chick:"), - - Nature34(2, 1, R.drawable.hatched_chick, ":hatched_chick:", - ":hatched_chick:"), - - Nature35(2, 1, R.drawable.hatching_chick, ":hatching_chick:", - ":hatching_chick:"), - - Nature36(2, 1, R.drawable.chicken, ":chicken:", ":chicken:"), - - Nature37(2, 1, R.drawable.penguin, ":penguin:", ":penguin:"), - - Nature38(2, 1, R.drawable.turtle, ":turtle:", ":turtle:"), - - Nature39(2, 1, R.drawable.bug, ":bug:", ":bug:"), - - Nature40(2, 1, R.drawable.honeybee, ":honeybee:", ":honeybee:"), - - Nature41(2, 1, R.drawable.ant, ":ant:", ":ant:"), - - Nature42(2, 1, R.drawable.beetle, ":beetle:", ":beetle:"), - - Nature43(2, 1, R.drawable.snail, ":snail:", ":snail:"), - - Nature44(2, 1, R.drawable.octopus, ":octopus:", ":octopus:"), - - Nature45(2, 1, R.drawable.tropical_fish, ":tropical_fish:", - ":tropical_fish:"), - - Nature46(2, 1, R.drawable.fish, ":fish:", ":fish:"), - - Nature47(2, 1, R.drawable.whale, ":whale:", ":whale:"), - - Nature48(2, 1, R.drawable.whale2, ":whale2:", ":whale2:"), - - Nature49(2, 1, R.drawable.dolphin, ":dolphin:", ":dolphin:"), - - Nature50(2, 1, R.drawable.cow2, ":cow2:", ":cow2:"), - - Nature51(2, 1, R.drawable.ram, ":ram:", ":ram:"), - - Nature52(2, 1, R.drawable.rat, ":rat:", ":rat:"), - - Nature53(2, 1, R.drawable.water_buffalo, ":water_buffalo:", - ":water_buffalo:"), - - Nature54(2, 1, R.drawable.tiger2, ":tiger2:", ":tiger2:"), - - Nature55(2, 1, R.drawable.rabbit2, ":rabbit2:", ":rabbit2:"), - - Nature56(2, 1, R.drawable.dragon, ":dragon:", ":dragon:"), - - Nature57(2, 1, R.drawable.goat, ":goat:", ":goat:"), - - Nature58(2, 1, R.drawable.rooster, ":rooster:", ":rooster:"), - - Nature59(2, 1, R.drawable.dog2, ":dog2:", ":dog2:"), - - Nature60(2, 1, R.drawable.pig2, ":pig2:", ":pig2:"), - - Nature61(2, 1, R.drawable.mouse2, ":mouse2:", ":mouse2:"), - - Nature62(2, 1, R.drawable.ox, ":ox:", ":ox:"), - - Nature63(2, 1, R.drawable.dragon_face, ":dragon_face:", ":dragon_face:"), - - Nature64(2, 1, R.drawable.blowfish, ":blowfish:", ":blowfish:"), - - Nature65(2, 1, R.drawable.crocodile, ":crocodile:", ":crocodile:"), - - Nature66(2, 1, R.drawable.dromedary_camel, ":dromedary_camel:", - ":dromedary_camel:"), - - Nature67(2, 1, R.drawable.leopard, ":leopard:", ":leopard:"), - - Nature68(2, 1, R.drawable.cat2, ":cat2:", ":cat2:"), - - Nature69(2, 1, R.drawable.poodle, ":poodle:", ":poodle:"), - - Nature70(2, 1, R.drawable.paw_prints, ":paw_prints:", ":paw_prints:"), - - Nature71(2, 1, R.drawable.bouquet, ":bouquet:", ":bouquet:"), - - Nature72(2, 1, R.drawable.cherry_blossom, ":cherry_blossom:", - ":cherry_blossom:"), - - Nature73(2, 1, R.drawable.tulip, ":tulip:", ":tulip:"), - - Nature74(2, 1, R.drawable.four_leaf_clover, ":four_leaf_clover:", - ":four_leaf_clover:"), - - Nature75(2, 1, R.drawable.rose, ":rose:", ":rose:"), - - Nature76(2, 1, R.drawable.sunflower, ":sunflower:", ":sunflower:"), - - Nature77(2, 1, R.drawable.hibiscus, ":hibiscus:", ":hibiscus:"), - - Nature78(2, 1, R.drawable.maple_leaf, ":maple_leaf:", ":maple_leaf:"), - - Nature79(2, 1, R.drawable.leaves, ":leaves:", ":leaves:"), - - Nature80(2, 1, R.drawable.fallen_leaf, ":fallen_leaf:", ":fallen_leaf:"), - - Nature81(2, 1, R.drawable.herb, ":herb:", ":herb:"), - - Nature82(2, 1, R.drawable.mushroom, ":mushroom:", ":mushroom:"), - - Nature83(2, 1, R.drawable.cactus, ":cactus:", ":cactus:"), - - Nature84(2, 1, R.drawable.palm_tree, ":palm_tree:", ":palm_tree:"), - - Nature85(2, 1, R.drawable.evergreen_tree, ":evergreen_tree:", - ":evergreen_tree:"), - - Nature86(2, 1, R.drawable.deciduous_tree, ":deciduous_tree:", - ":deciduous_tree:"), - - Nature87(2, 1, R.drawable.chestnut, ":chestnut:", ":chestnut:"), - - Nature88(2, 1, R.drawable.seedling, ":seedling:", ":seedling:"), - - Nature89(2, 1, R.drawable.blossom, ":blossom:", ":blossom:"), - - Nature90(2, 1, R.drawable.ear_of_rice, ":ear_of_rice:", ":ear_of_rice:"), - - Nature91(2, 1, R.drawable.shell, ":shell:", ":shell:"), - - Nature92(2, 1, R.drawable.globe_with_meridians, ":globe_with_meridians:", - ":globe_with_meridians:"), - - Nature93(2, 1, R.drawable.sun_with_face, ":sun_with_face:", - ":sun_with_face:"), - - Nature94(2, 1, R.drawable.full_moon_with_face, ":full_moon_with_face:", - ":full_moon_with_face:"), - - Nature95(2, 1, R.drawable.new_moon_with_face, ":new_moon_with_face:", - ":new_moon_with_face:"), - - Nature96(2, 1, R.drawable.new_moon, ":new_moon:", ":new_moon:"), - - Nature97(2, 1, R.drawable.waxing_crescent_moon, ":waxing_crescent_moon:", - ":waxing_crescent_moon:"), - - Nature98(2, 1, R.drawable.first_quarter_moon, ":first_quarter_moon:", - ":first_quarter_moon:"), - - Nature99(2, 1, R.drawable.waxing_gibbous_moon, ":waxing_gibbous_moon:", - ":waxing_gibbous_moon:"), - - Nature100(2, 1, R.drawable.full_moon, ":full_moon:", ":full_moon:"), - - Nature101(2, 1, R.drawable.waning_gibbous_moon, ":waning_gibbous_moon:", - ":waning_gibbous_moon:"), - - Nature102(2, 1, R.drawable.last_quarter_moon, ":last_quarter_moon:", - ":last_quarter_moon:"), - - Nature103(2, 1, R.drawable.waning_crescent_moon, ":waning_crescent_moon:", - ":waning_crescent_moon:"), - - Nature104(2, 1, R.drawable.last_quarter_moon_with_face, - ":last_quarter_moon_with_face:", ":last_quarter_moon_with_face:"), - - Nature105(2, 1, R.drawable.first_quarter_moon_with_face, - ":first_quarter_moon_with_face:", ":first_quarter_moon_with_face:"), - - Nature106(2, 1, R.drawable.moon, ":moon:", ":moon:"), - - Nature107(2, 1, R.drawable.earth_africa, ":earth_africa:", ":earth_africa:"), - - Nature108(2, 1, R.drawable.earth_americas, ":earth_americas:", - ":earth_americas:"), - - Nature109(2, 1, R.drawable.earth_asia, ":earth_asia:", ":earth_asia:"), - - Nature110(2, 1, R.drawable.volcano, ":volcano:", ":volcano:"), - - Nature111(2, 1, R.drawable.milky_way, ":milky_way:", ":milky_way:"), - - Nature112(2, 1, R.drawable.partly_sunny, ":partly_sunny:", ":partly_sunny:"), - - Nature113(2, 1, R.drawable.octocat, ":octocat:", ":octocat:"), - - Nature114(2, 1, R.drawable.squirrel, ":squirrel:", ":squirrel:"); - - /********************************* 操作 **************************************/ + KJEMOJI0(0, 1, R.mipmap.smiley_0, "[微笑]", "[0]"), + KJEMOJI1(0, 1, R.mipmap.smiley_1, "[撇嘴]", "[1]"), + KJEMOJI2(0, 1, R.mipmap.smiley_2, "[色]", "[2]"), + KJEMOJI3(0, 1, R.mipmap.smiley_3, "[发呆]", "[3]"), + KJEMOJI4(0, 1, R.mipmap.smiley_4, "[得意]", "[4]"), + KJEMOJI5(0, 1, R.mipmap.smiley_5, "[流泪]", "[5]"), + KJEMOJI6(0, 1, R.mipmap.smiley_6, "[害羞]", "[6]"), + KJEMOJI7(0, 1, R.mipmap.smiley_7, "[闭嘴]", "[7]"), + KJEMOJI8(0, 1, R.mipmap.smiley_8, "[睡]", "[8]"), + KJEMOJI9(0, 1, R.mipmap.smiley_9, "[大哭]", "[9]"), + KJEMOJI10(0, 1, R.mipmap.smiley_10, "[尴尬]", "[10]"), + KJEMOJI11(0, 1, R.mipmap.smiley_11, "[发怒]", "[11]"), + KJEMOJI12(0, 1, R.mipmap.smiley_12, "[调皮]", "[12]"), + KJEMOJI13(0, 1, R.mipmap.smiley_13, "[呲牙]", "[13]"), + KJEMOJI14(0, 1, R.mipmap.smiley_14, "[惊讶]", "[14]"), + KJEMOJI15(0, 1, R.mipmap.smiley_15, "[难过]", "[15]"), + KJEMOJI16(0, 1, R.mipmap.smiley_16, "[酷]", "[16]"), + KJEMOJI17(0, 1, R.mipmap.smiley_17, "[冷汗]", "[17]"), + KJEMOJI18(0, 1, R.mipmap.smiley_18, "[抓狂]", "[18]"), + KJEMOJI19(0, 1, R.mipmap.smiley_19, "[吐]", "[19]"), + KJEMOJI20(0, 1, R.mipmap.smiley_20, "[偷笑]", "[20]"), + KJEMOJI21(0, 1, R.mipmap.smiley_21, "[可爱]", "[21]"), + KJEMOJI22(0, 1, R.mipmap.smiley_22, "[白眼]", "[22]"), + KJEMOJI23(0, 1, R.mipmap.smiley_23, "[傲慢]", "[23]"), + KJEMOJI24(0, 1, R.mipmap.smiley_24, "[饥饿]", "[24]"), + KJEMOJI25(0, 1, R.mipmap.smiley_25, "[困]", "[25]"), + KJEMOJI26(0, 1, R.mipmap.smiley_26, "[惊恐]", "[26]"), + KJEMOJI27(0, 1, R.mipmap.smiley_27, "[流汗]", "[27]"), + KJEMOJI28(0, 1, R.mipmap.smiley_28, "[憨笑]", "[28]"), + KJEMOJI29(0, 1, R.mipmap.smiley_29, "[大兵]", "[29]"), + KJEMOJI30(0, 1, R.mipmap.smiley_30, "[奋斗]", "[30]"), + KJEMOJI31(0, 1, R.mipmap.smiley_31, "[咒骂]", "[31]"), + KJEMOJI32(0, 1, R.mipmap.smiley_32, "[疑问]", "[32]"), + KJEMOJI33(0, 1, R.mipmap.smiley_33, "[嘘]", "[33]"), + KJEMOJI34(0, 1, R.mipmap.smiley_34, "[晕]", "[34]"), + KJEMOJI35(0, 1, R.mipmap.smiley_35, "[折磨]", "[35]"), + KJEMOJI36(0, 1, R.mipmap.smiley_36, "[衰]", "[36]"), + KJEMOJI37(0, 1, R.mipmap.smiley_37, "[骷髅]", "[37]"), + KJEMOJI38(0, 1, R.mipmap.smiley_38, "[敲打]", "[38]"), + KJEMOJI39(0, 1, R.mipmap.smiley_39, "[再见]", "[39]"), + KJEMOJI40(0, 1, R.mipmap.smiley_40, "[擦汗]", "[40]"), + KJEMOJI41(0, 1, R.mipmap.smiley_41, "[抠鼻]", "[41]"), + KJEMOJI42(0, 1, R.mipmap.smiley_42, "[鼓掌]", "[42]"), + KJEMOJI43(0, 1, R.mipmap.smiley_43, "[糗大了]", "[43]"), + KJEMOJI44(0, 1, R.mipmap.smiley_44, "[坏笑]", "[44]"), + KJEMOJI45(0, 1, R.mipmap.smiley_45, "[左哼哼]", "[45]"), + KJEMOJI46(0, 1, R.mipmap.smiley_46, "[右哼哼]", "[46]"), + KJEMOJI47(0, 1, R.mipmap.smiley_47, "[哈欠]", "[47]"), + KJEMOJI48(0, 1, R.mipmap.smiley_48, "[鄙视]", "[48]"), + KJEMOJI49(0, 1, R.mipmap.smiley_49, "[委屈]", "[49]"), + KJEMOJI50(0, 1, R.mipmap.smiley_50, "[快哭了]", "[50]"), + KJEMOJI51(0, 1, R.mipmap.smiley_51, "[阴险]", "[51]"), + KJEMOJI52(0, 1, R.mipmap.smiley_52, "[亲亲]", "[52]"), + KJEMOJI53(0, 1, R.mipmap.smiley_53, "[吓]", "[53]"), + KJEMOJI54(0, 1, R.mipmap.smiley_54, "[可怜]", "[54]"), + KJEMOJI55(0, 1, R.mipmap.smiley_55, "[菜刀]", "[55]"), + KJEMOJI56(0, 1, R.mipmap.smiley_56, "[西瓜]", "[56]"), + KJEMOJI57(0, 1, R.mipmap.smiley_57, "[啤酒]", "[57]"), + KJEMOJI58(0, 1, R.mipmap.smiley_58, "[篮球]", "[58]"), + KJEMOJI59(0, 1, R.mipmap.smiley_59, "[乒乓]", "[59]"), + KJEMOJI60(0, 1, R.mipmap.smiley_60, "[咖啡]", "[60]"), + KJEMOJI61(0, 1, R.mipmap.smiley_61, "[饭]", "[61]"), + KJEMOJI62(0, 1, R.mipmap.smiley_62, "[猪头]", "[62]"), + KJEMOJI63(0, 1, R.mipmap.smiley_63, "[玫瑰]", "[63]"), + KJEMOJI64(0, 1, R.mipmap.smiley_64, "[凋谢]", "[64]"), + KJEMOJI65(0, 1, R.mipmap.smiley_65, "[嘴唇]", "[65]"), + KJEMOJI66(0, 1, R.mipmap.smiley_66, "[爱心]", "[66]"), + KJEMOJI67(0, 1, R.mipmap.smiley_67, "[心碎]", "[67]"), + KJEMOJI68(0, 1, R.mipmap.smiley_68, "[蛋糕]", "[68]"), + KJEMOJI69(0, 1, R.mipmap.smiley_69, "[闪电]", "[69]"), + KJEMOJI70(0, 1, R.mipmap.smiley_70, "[炸弹]", "[70]"), + KJEMOJI71(0, 1, R.mipmap.smiley_71, "[刀]", "[71]"), + KJEMOJI72(0, 1, R.mipmap.smiley_72, "[足球]", "[72]"), + KJEMOJI73(0, 1, R.mipmap.smiley_73, "[瓢虫]", "[73]"), + KJEMOJI74(0, 1, R.mipmap.smiley_74, "[便便]", "[74]"), + KJEMOJI75(0, 1, R.mipmap.smiley_75, "[月亮]", "[75]"), + KJEMOJI76(0, 1, R.mipmap.smiley_76, "[太阳]", "[76]"), + KJEMOJI77(0, 1, R.mipmap.smiley_77, "[礼物]", "[77]"), + KJEMOJI78(0, 1, R.mipmap.smiley_78, "[拥抱]", "[78]"), + KJEMOJI79(0, 1, R.mipmap.smiley_79, "[强]", "[79]"), + KJEMOJI80(0, 1, R.mipmap.smiley_80, "[弱]", "[80]"), + KJEMOJI81(0, 1, R.mipmap.smiley_81, "[握手]", "[81]"), + KJEMOJI82(0, 1, R.mipmap.smiley_82, "[胜利]", "[82]"), + KJEMOJI83(0, 1, R.mipmap.smiley_83, "[抱拳]", "[83]"), + KJEMOJI84(0, 1, R.mipmap.smiley_84, "[勾引]", "[84]"), + KJEMOJI85(0, 1, R.mipmap.smiley_85, "[拳头]", "[85]"), + KJEMOJI86(0, 1, R.mipmap.smiley_86, "[差劲]", "[86]"), + KJEMOJI87(0, 1, R.mipmap.smiley_87, "[爱你]", "[87]"), + KJEMOJI88(0, 1, R.mipmap.smiley_88, "[NO]", "[88]"), + KJEMOJI89(0, 1, R.mipmap.smiley_89, "[OK]", "[89]"), + KJEMOJI90(0, 1, R.mipmap.smiley_90, "[爱情]", "[90]"), + KJEMOJI91(0, 1, R.mipmap.smiley_91, "[飞吻]", "[91]"), + KJEMOJI92(0, 1, R.mipmap.smiley_92, "[跳跳]", "[92]"), + KJEMOJI93(0, 1, R.mipmap.smiley_93, "[发抖]", "[93]"), + KJEMOJI94(0, 1, R.mipmap.smiley_94, "[怄火]", "[94]"), + KJEMOJI95(0, 1, R.mipmap.smiley_95, "[转圈]", "[95]"), + KJEMOJI96(0, 1, R.mipmap.smiley_96, "[磕头]", "[96]"), + KJEMOJI97(0, 1, R.mipmap.smiley_97, "[回头]", "[97]"), + KJEMOJI98(0, 1, R.mipmap.smiley_98, "[跳绳]", "[98]"), + KJEMOJI99(0, 1, R.mipmap.smiley_99, "[投降]", "[99]"), + KJEMOJI100(0, 1, R.mipmap.smiley_100, "[激动]", "[100]"), + KJEMOJI101(0, 1, R.mipmap.smiley_101, "[乱舞]", "[101]"), + KJEMOJI102(0, 1, R.mipmap.smiley_102, "[献吻]", "[102]"), + KJEMOJI103(0, 1, R.mipmap.smiley_103, "[左太极]", "[103]"), + KJEMOJI104(0, 1, R.mipmap.smiley_104, "[右太极]", "[104]"), + + GITHUB0(1, 1, R.mipmap.bowtie, ":bowtie:", ":bowtie:"), + GITHUB1(1, 1, R.mipmap.smile, ":smile:", ":smile:"), + GITHUB2(1, 1, R.mipmap.laughing, ":laughing:", ":laughing:"), + GITHUB3(1, 1, R.mipmap.blush, ":blush:", ":blush:"), + GITHUB4(1, 1, R.mipmap.smiley, ":smiley:", ":smiley:"), + GITHUB5(1, 1, R.mipmap.relaxed, ":relaxed:", ":relaxed:"), + GITHUB6(1, 1, R.mipmap.smirk, ":smirk:", ":smirk:"), + GITHUB7(1, 1, R.mipmap.heart_eyes, ":heart_eyes:", ":heart_eyes:"), + GITHUB8(1, 1, R.mipmap.kissing_heart, ":kissing_heart:", ":kissing_heart:"), + GITHUB9(1, 1, R.mipmap.kissing_closed_eyes, ":kissing_closed_eyes:", ":kissing_closed_eyes:"), + GITHUB10(1, 1, R.mipmap.flushed, ":flushed:", ":flushed:"), + GITHUB11(1, 1, R.mipmap.relieved, ":relieved:", ":relieved:"), + GITHUB12(1, 1, R.mipmap.satisfied, ":satisfied:", ":satisfied:"), + GITHUB13(1, 1, R.mipmap.grin, ":grin:", ":grin:"), + GITHUB14(1, 1, R.mipmap.wink, ":wink:", ":wink:"), + GITHUB15(1, 1, R.mipmap.stuck_out_tongue_winking_eye, ":stuck_out_tongue_winking_eye:", ":stuck_out_tongue_winking_eye:"), + GITHUB16(1, 1, R.mipmap.stuck_out_tongue_closed_eyes, ":stuck_out_tongue_closed_eyes:", ":stuck_out_tongue_closed_eyes:"), + GITHUB17(1, 1, R.mipmap.grinning, ":grinning:", ":grinning:"), + GITHUB18(1, 1, R.mipmap.kissing, ":kissing:", ":kissing:"), + GITHUB19(1, 1, R.mipmap.kissing_smiling_eyes, ":kissing_smiling_eyes:", ":kissing_smiling_eyes:"), + GITHUB20(1, 1, R.mipmap.stuck_out_tongue, ":stuck_out_tongue:", ":stuck_out_tongue:"), + GITHUB21(1, 1, R.mipmap.sleeping, ":sleeping:", ":sleeping:"), + GITHUB22(1, 1, R.mipmap.worried, ":worried:", ":worried:"), + GITHUB23(1, 1, R.mipmap.frowning, ":frowning:", ":frowning:"), + GITHUB24(1, 1, R.mipmap.anguished, ":anguished:", ":anguished:"), + GITHUB25(1, 1, R.mipmap.open_mouth, ":open_mouth:", ":open_mouth:"), + GITHUB26(1, 1, R.mipmap.grimacing, ":grimacing:", ":grimacing:"), + GITHUB27(1, 1, R.mipmap.confused, ":confused:", ":confused:"), + GITHUB28(1, 1, R.mipmap.hushed, ":hushed:", ":hushed:"), + GITHUB29(1, 1, R.mipmap.expressionless, ":expressionless:", ":expressionless:"), + GITHUB30(1, 1, R.mipmap.unamused, ":unamused:", ":unamused:"), + GITHUB31(1, 1, R.mipmap.sweat_smile, ":sweat_smile:", ":sweat_smile:"), + GITHUB32(1, 1, R.mipmap.sweat, ":sweat:", ":sweat:"), + GITHUB33(1, 1, R.mipmap.disappointed_relieved, ":disappointed_relieved:", ":disappointed_relieved:"), + GITHUB34(1, 1, R.mipmap.weary, ":weary:", ":weary:"), + GITHUB35(1, 1, R.mipmap.pensive, ":pensive:", ":pensive:"), + GITHUB36(1, 1, R.mipmap.disappointed, ":disappointed:", ":disappointed:"), + GITHUB37(1, 1, R.mipmap.confounded, ":confounded:", ":confounded:"), + GITHUB38(1, 1, R.mipmap.fearful, ":fearful:", ":fearful:"), + GITHUB39(1, 1, R.mipmap.cold_sweat, ":cold_sweat:", ":cold_sweat:"), + GITHUB40(1, 1, R.mipmap.persevere, ":persevere:", ":persevere:"), + GITHUB41(1, 1, R.mipmap.cry, ":cry:", ":cry:"), + GITHUB42(1, 1, R.mipmap.sob, ":sob:", ":sob:"), + GITHUB43(1, 1, R.mipmap.joy, ":joy:", ":joy:"), + GITHUB44(1, 1, R.mipmap.astonished, ":astonished:", ":astonished:"), + GITHUB45(1, 1, R.mipmap.scream, ":scream:", ":scream:"), + GITHUB46(1, 1, R.mipmap.neckbeard, ":neckbeard:", ":neckbeard:"), + GITHUB47(1, 1, R.mipmap.tired_face, ":tired_face:", ":tired_face:"), + GITHUB48(1, 1, R.mipmap.angry, ":angry:", ":angry:"), + GITHUB49(1, 1, R.mipmap.rage, ":rage:", ":rage:"), + GITHUB50(1, 1, R.mipmap.triumph, ":triumph:", ":triumph:"), + GITHUB51(1, 1, R.mipmap.sleepy, ":sleepy:", ":sleepy:"), + GITHUB52(1, 1, R.mipmap.yum, ":yum:", ":yum:"), + GITHUB53(1, 1, R.mipmap.mask, ":mask:", ":mask:"), + GITHUB54(1, 1, R.mipmap.sunglasses, ":sunglasses:", ":sunglasses:"), + GITHUB55(1, 1, R.mipmap.dizzy_face, ":dizzy_face:", ":dizzy_face:"), + GITHUB56(1, 1, R.mipmap.imp, ":imp:", ":imp:"), + GITHUB57(1, 1, R.mipmap.smiling_imp, ":smiling_imp:", ":smiling_imp:"), + GITHUB58(1, 1, R.mipmap.neutral_face, ":neutral_face:", ":neutral_face:"), + GITHUB59(1, 1, R.mipmap.no_mouth, ":no_mouth:", ":no_mouth:"), + GITHUB60(1, 1, R.mipmap.innocent, ":innocent:", ":innocent:"), + GITHUB61(1, 1, R.mipmap.alien, ":alien:", ":alien:"), + GITHUB62(1, 1, R.mipmap.yellow_heart, ":yellow_heart:", ":yellow_heart:"), + GITHUB63(1, 1, R.mipmap.blue_heart, ":blue_heart:", ":blue_heart:"), + GITHUB64(1, 1, R.mipmap.purple_heart, ":purple_heart:", ":purple_hert:"), + GITHUB65(1, 1, R.mipmap.heart, ":heart:", ":heart:"), + GITHUB66(1, 1, R.mipmap.green_heart, ":green_heart:", ":green_heart:"), + GITHUB67(1, 1, R.mipmap.broken_heart, ":broken_heart:", ":broken_heart:"), + GITHUB68(1, 1, R.mipmap.heartbeat, ":heartbeat:", ":heartbeat:"), + GITHUB69(1, 1, R.mipmap.heartpulse, ":heartpulse:", ":heartpulse:"), + GITHUB70(1, 1, R.mipmap.two_hearts, ":two_hearts:", ":two_hearts:"), + GITHUB71(1, 1, R.mipmap.revolving_hearts, ":revolving_hearts:", ":revolving_hearts:"), + GITHUB72(1, 1, R.mipmap.cupid, ":cupid:", ":cupid:"), + GITHUB73(1, 1, R.mipmap.sparkling_heart, ":sparkling_heart:", ":sparkling_heart:"), + GITHUB74(1, 1, R.mipmap.sparkles, ":sparkles:", ":sparkles:"), + GITHUB75(1, 1, R.mipmap.star, ":star:", ":star:"), + GITHUB76(1, 1, R.mipmap.star2, ":star2:", ":star2:"), + GITHUB77(1, 1, R.mipmap.dizzy, ":dizzy:", ":dizzy:"), + GITHUB78(1, 1, R.mipmap.boom, ":boom:", ":boom:"), + GITHUB79(1, 1, R.mipmap.collision, ":collision:", ":collision:"), + GITHUB80(1, 1, R.mipmap.anger, ":anger:", ":anger:"), + GITHUB81(1, 1, R.mipmap.exclamation, ":exclamation:", ":exclamation:"), + GITHUB82(1, 1, R.mipmap.question, ":question:", ":question:"), + GITHUB83(1, 1, R.mipmap.grey_exclamation, ":grey_exclamation:", ":grey_exclamation:"), + GITHUB84(1, 1, R.mipmap.grey_question, ":grey_question:", ":grey_question:"), + GITHUB85(1, 1, R.mipmap.zzz, ":zzz:", ":zzz:"), + GITHUB86(1, 1, R.mipmap.dash, ":dash:", ":dash:"), + GITHUB87(1, 1, R.mipmap.sweat_drops, ":sweat_drops:", ":sweat_drops:"), + GITHUB88(1, 1, R.mipmap.notes, ":notes:", ":notes:"), + GITHUB89(1, 1, R.mipmap.musical_note, ":musical_note:", ":musical_note:"), + GITHUB90(1, 1, R.mipmap.fire, ":fire:", ":fire:"), + GITHUB91(1, 1, R.mipmap.hankey, ":hankey:", ":hankey:"), + GITHUB92(1, 1, R.mipmap.poop, ":poop:", ":poop:"), + GITHUB93(1, 1, R.mipmap.shit, ":shit:", ":shit:"), + GITHUB94(1, 1, R.mipmap.thumbsup, ":+1:", ":+1:"), + GITHUB95(1, 1, R.mipmap.thumbsup, ":thumbsup:", ":thumbsup:"), + GITHUB96(1, 1, R.mipmap.the_1, ":-1:", ":-1:"), + GITHUB97(1, 1, R.mipmap.thumbsdown, ":thumbsdown:", ":thumbsdown:"), + GITHUB98(1, 1, R.mipmap.ok_hand, ":ok_hand:", ":ok_hand:"), + GITHUB99(1, 1, R.mipmap.punch, ":punch:", ":punch:"), + GITHUB100(1, 1, R.mipmap.facepunch, ":facepunch:", ":facepunch:"), + GITHUB101(1, 1, R.mipmap.fist, ":fist:", ":fist:"), + GITHUB102(1, 1, R.mipmap.v, ":v:", ":v:"), + GITHUB103(1, 1, R.mipmap.wave, ":wave:", ":wave:"), + GITHUB104(1, 1, R.mipmap.hand, ":hand:", ":hand:"), + GITHUB105(1, 1, R.mipmap.raised_hand, ":raised_hand:", ":raised_hand:"), + GITHUB106(1, 1, R.mipmap.open_hands, ":open_hands:", ":open_hands:"), + GITHUB107(1, 1, R.mipmap.point_up, ":point_up:", ":point_up:"), + GITHUB108(1, 1, R.mipmap.point_down, ":point_down:", ":point_down:"), + GITHUB109(1, 1, R.mipmap.point_left, ":point_left:", ":point_left:"), + GITHUB110(1, 1, R.mipmap.point_right, ":point_right:", ":point_right:"), + GITHUB111(1, 1, R.mipmap.raised_hands, ":raised_hands:", ":raised_hands:"), + GITHUB112(1, 1, R.mipmap.pray, ":pray:", ":pray:"), + GITHUB113(1, 1, R.mipmap.point_up_2, ":point_up_2:", ":point_up_2:"), + GITHUB114(1, 1, R.mipmap.clap, ":clap:", ":clap:"), + GITHUB115(1, 1, R.mipmap.muscle, ":muscle:", ":muscle:"), + GITHUB116(1, 1, R.mipmap.metal, ":metal:", ":metal:"), + GITHUB117(1, 1, R.mipmap.fu, ":fu:", ":fu:"), + GITHUB118(1, 1, R.mipmap.walking, ":walking:", ":walking:"), + GITHUB119(1, 1, R.mipmap.runner, ":runner:", ":runner:"), + GITHUB120(1, 1, R.mipmap.running, ":running:", ":running:"), + GITHUB121(1, 1, R.mipmap.couple, ":couple:", ":couple:"), + GITHUB122(1, 1, R.mipmap.family, ":family:", ":family:"), + GITHUB123(1, 1, R.mipmap.two_men_holding_hands, ":two_men_holding_hands:", ":two_men_holding_hands:"), + GITHUB124(1, 1, R.mipmap.two_women_holding_hands, ":two_women_holding_hands:", ":two_women_holding_hands:"), + GITHUB125(1, 1, R.mipmap.dancer, ":dancer:", ":dancer:"), + GITHUB126(1, 1, R.mipmap.dancers, ":dancers:", ":dancers:"), + GITHUB127(1, 1, R.mipmap.ok_woman, ":ok_woman:", ":ok_woman:"), + GITHUB128(1, 1, R.mipmap.no_good, ":no_good:", ":no_good:"), + GITHUB129(1, 1, R.mipmap.information_desk_person, ":information_desk_person:", ":information_desk_person:"), + GITHUB130(1, 1, R.mipmap.raising_hand, ":raising_hand:", ":raising_hand:"), + GITHUB131(1, 1, R.mipmap.bride_with_veil, ":bride_with_veil:", ":bride_with_veil:"), + GITHUB132(1, 1, R.mipmap.person_with_pouting_face, ":person_with_pouting_face:", ":person_with_pouting_face:"), + GITHUB133(1, 1, R.mipmap.person_frowning, ":person_frowning:", ":person_frowning:"), + GITHUB134(1, 1, R.mipmap.bow, ":bow:", ":bow:"), + GITHUB135(1, 1, R.mipmap.couplekiss, ":couplekiss:", ":couplekiss:"), + GITHUB136(1, 1, R.mipmap.couple_with_heart, ":couple_with_heart:", ":couple_with_heart:"), + GITHUB137(1, 1, R.mipmap.massage, ":massage:", ":massage:"), + GITHUB138(1, 1, R.mipmap.haircut, ":haircut:", ":haircut:"), + GITHUB139(1, 1, R.mipmap.nail_care, ":nail_care:", ":nail_care:"), + GITHUB140(1, 1, R.mipmap.boy, ":boy:", ":boy:"), + GITHUB141(1, 1, R.mipmap.girl, ":girl:", ":girl:"), + GITHUB142(1, 1, R.mipmap.woman, ":woman:", ":woman:"), + GITHUB143(1, 1, R.mipmap.man, ":man:", ":man:"), + GITHUB144(1, 1, R.mipmap.baby, ":baby:", ":baby:"), + GITHUB145(1, 1, R.mipmap.older_woman, ":older_woman:", ":older_woman:"), + GITHUB146(1, 1, R.mipmap.older_man, ":older_man:", ":older_man:"), + GITHUB147(1, 1, R.mipmap.person_with_blond_hair, ":person_with_blond_hair:", ":person_with_blond_hair:"), + GITHUB148(1, 1, R.mipmap.man_with_gua_pi_mao, ":man_with_gua_pi_mao:", ":man_with_gua_pi_mao:"), + GITHUB149(1, 1, R.mipmap.man_with_turban, ":man_with_turban:", ":man_with_turban:"), + GITHUB150(1, 1, R.mipmap.construction_worker, ":construction_worker:", ":construction_worker:"), + GITHUB151(1, 1, R.mipmap.cop, ":cop:", ":cop:"), + GITHUB152(1, 1, R.mipmap.angel, ":angel:", ":angel:"), + GITHUB153(1, 1, R.mipmap.princess, ":princess:", ":princess:"), + GITHUB154(1, 1, R.mipmap.smiley_cat, ":smiley_cat:", ":smiley_cat:"), + GITHUB155(1, 1, R.mipmap.smile_cat, ":smile_cat:", ":smile_cat:"), + GITHUB156(1, 1, R.mipmap.heart_eyes_cat, ":heart_eyes_cat:", ":heart_eyes_cat:"), + GITHUB157(1, 1, R.mipmap.kissing_cat, ":kissing_cat:", ":kissing_cat:"), + GITHUB158(1, 1, R.mipmap.smirk_cat, ":smirk_cat:", ":smirk_cat:"), + GITHUB159(1, 1, R.mipmap.scream_cat, ":scream_cat:", ":scream_cat:"), + GITHUB160(1, 1, R.mipmap.crying_cat_face, ":crying_cat_face:", ":crying_cat_face:"), + GITHUB161(1, 1, R.mipmap.joy_cat, ":joy_cat:", ":joy_cat:"), + GITHUB162(1, 1, R.mipmap.pouting_cat, ":pouting_cat:", ":pouting_cat:"), + GITHUB163(1, 1, R.mipmap.japanese_ogre, ":japanese_ogre:", ":japanese_ogre:"), + GITHUB164(1, 1, R.mipmap.japanese_goblin, ":japanese_goblin:", ":japanese_goblin:"), + GITHUB165(1, 1, R.mipmap.see_no_evil, ":see_no_evil:", ":see_no_evil:"), + GITHUB166(1, 1, R.mipmap.hear_no_evil, ":hear_no_evil:", ":hear_no_evil:"), + GITHUB167(1, 1, R.mipmap.speak_no_evil, ":speak_no_evil:", ":speak_no_evil:"), + GITHUB168(1, 1, R.mipmap.guardsman, ":guardsman:", ":guardsman:"), + GITHUB169(1, 1, R.mipmap.skull, ":skull:", ":skull:"), + GITHUB170(1, 1, R.mipmap.feet, ":feet:", ":feet:"), + GITHUB171(1, 1, R.mipmap.lips, ":lips:", ":lips:"), + GITHUB172(1, 1, R.mipmap.kiss, ":kiss:", ":kiss:"), + GITHUB173(1, 1, R.mipmap.droplet, ":droplet:", ":droplet:"), + GITHUB174(1, 1, R.mipmap.ear, ":ear:", ":ear:"), + GITHUB175(1, 1, R.mipmap.eyes, ":eyes:", ":eyes:"), + GITHUB176(1, 1, R.mipmap.nose, ":nose:", ":nose:"), + GITHUB177(1, 1, R.mipmap.tongue, ":tongue:", ":tongue:"), + GITHUB178(1, 1, R.mipmap.love_letter, ":love_letter:", ":love_letter:"), + GITHUB179(1, 1, R.mipmap.bust_in_silhouette, ":bust_in_silhouette:", ":bust_in_silhouette:"), + GITHUB180(1, 1, R.mipmap.busts_in_silhouette, ":busts_in_silhouette:", ":busts_in_silhouette:"), + GITHUB181(1, 1, R.mipmap.speech_balloon, ":speech_balloon:", ":speech_balloon:"), + GITHUB182(1, 1, R.mipmap.thought_balloon, ":thought_balloon:", ":thought_balloon:"), + GITHUB183(1, 1, R.mipmap.feelsgood, ":feelsgood:", ":feelsgood:"), + GITHUB184(1, 1, R.mipmap.finnadie, ":finnadie:", ":finnadie:"), + GITHUB185(1, 1, R.mipmap.goberserk, ":goberserk:", ":goberserk:"), + GITHUB186(1, 1, R.mipmap.godmode, ":godmode:", ":godmode:"), + GITHUB187(1, 1, R.mipmap.hurtrealbad, ":hurtrealbad:", ":hurtrealbad:"), + GITHUB188(1, 1, R.mipmap.rage1, ":rage1:", ":rage1:"), + GITHUB189(1, 1, R.mipmap.rage2, ":rage2:", ":rage2:"), + GITHUB190(1, 1, R.mipmap.rage3, ":rage3:", ":rage3:"), + GITHUB191(1, 1, R.mipmap.rage4, ":rage4:", ":rage4:"), + GITHUB192(1, 1, R.mipmap.suspect, ":suspect:", ":suspect:"), + GITHUB193(1, 1, R.mipmap.trollface, ":trollface:", ":trollface:"), + + Nature0(2, 1, R.mipmap.sunny, ":sunny:", ":sunny:"), + Nature1(2, 1, R.mipmap.umbrella, ":umbrella:", ":umbrella:"), + Nature2(2, 1, R.mipmap.cloud, ":cloud:", ":cloud:"), + Nature3(2, 1, R.mipmap.snowflake, ":snowflake:", ":snowflake:"), + Nature4(2, 1, R.mipmap.snowman, ":snowman:", ":snowman:"), + Nature5(2, 1, R.mipmap.zap, ":zap:", ":zap:"), + Nature6(2, 1, R.mipmap.cyclone, ":cyclone:", ":cyclone:"), + Nature7(2, 1, R.mipmap.foggy, ":foggy:", ":foggy:"), + Nature8(2, 1, R.mipmap.ocean, ":ocean:", ":ocean:"), + Nature9(2, 1, R.mipmap.cat, ":cat:", ":cat:"), + Nature10(2, 1, R.mipmap.dog, ":dog:", ":dog:"), + Nature11(2, 1, R.mipmap.mouse, ":mouse:", ":mouse:"), + Nature12(2, 1, R.mipmap.hamster, ":hamster:", ":hamster:"), + Nature13(2, 1, R.mipmap.rabbit, ":rabbit:", ":rabbit:"), + Nature14(2, 1, R.mipmap.wolf, ":wolf:", ":wolf:"), + Nature15(2, 1, R.mipmap.frog, ":frog:", ":frog:"), + Nature16(2, 1, R.mipmap.tiger, ":tiger:", ":tiger:"), + Nature17(2, 1, R.mipmap.koala, ":koala:", ":koala:"), + Nature18(2, 1, R.mipmap.bear, ":bear:", ":bear:"), + Nature19(2, 1, R.mipmap.pig, ":pig:", ":pig:"), + Nature20(2, 1, R.mipmap.pig_nose, ":pig_nose:", ":pig_nose:"), + Nature21(2, 1, R.mipmap.cow, ":cow:", ":cow:"), + Nature22(2, 1, R.mipmap.boar, ":boar:", ":boar:"), + Nature23(2, 1, R.mipmap.monkey_face, ":monkey_face:", ":monkey_face:"), + Nature24(2, 1, R.mipmap.monkey, ":monkey:", ":monkey:"), + Nature25(2, 1, R.mipmap.horse, ":horse:", ":horse:"), + Nature26(2, 1, R.mipmap.racehorse, ":racehorse:", ":racehorse:"), + Nature27(2, 1, R.mipmap.camel, ":camel:", ":camel:"), + Nature28(2, 1, R.mipmap.sheep, ":sheep:", ":sheep:"), + Nature29(2, 1, R.mipmap.elephant, ":elephant:", ":elephant:"), + Nature30(2, 1, R.mipmap.panda_face, ":panda_face:", ":panda_face:"), + Nature31(2, 1, R.mipmap.snake, ":snake:", ":snake:"), + Nature32(2, 1, R.mipmap.bird, ":bird:", ":bird:"), + Nature33(2, 1, R.mipmap.baby_chick, ":baby_chick:", ":baby_chick:"), + Nature34(2, 1, R.mipmap.hatched_chick, ":hatched_chick:", ":hatched_chick:"), + Nature35(2, 1, R.mipmap.hatching_chick, ":hatching_chick:", ":hatching_chick:"), + Nature36(2, 1, R.mipmap.chicken, ":chicken:", ":chicken:"), + Nature37(2, 1, R.mipmap.penguin, ":penguin:", ":penguin:"), + Nature38(2, 1, R.mipmap.turtle, ":turtle:", ":turtle:"), + Nature39(2, 1, R.mipmap.bug, ":bug:", ":bug:"), + Nature40(2, 1, R.mipmap.honeybee, ":honeybee:", ":honeybee:"), + Nature41(2, 1, R.mipmap.ant, ":ant:", ":ant:"), + Nature42(2, 1, R.mipmap.beetle, ":beetle:", ":beetle:"), + Nature43(2, 1, R.mipmap.snail, ":snail:", ":snail:"), + Nature44(2, 1, R.mipmap.octopus, ":octopus:", ":octopus:"), + Nature45(2, 1, R.mipmap.tropical_fish, ":tropical_fish:", ":tropical_fish:"), + Nature46(2, 1, R.mipmap.fish, ":fish:", ":fish:"), + Nature47(2, 1, R.mipmap.whale, ":whale:", ":whale:"), + Nature48(2, 1, R.mipmap.whale2, ":whale2:", ":whale2:"), + Nature49(2, 1, R.mipmap.dolphin, ":dolphin:", ":dolphin:"), + Nature50(2, 1, R.mipmap.cow2, ":cow2:", ":cow2:"), + Nature51(2, 1, R.mipmap.ram, ":ram:", ":ram:"), + Nature52(2, 1, R.mipmap.rat, ":rat:", ":rat:"), + Nature53(2, 1, R.mipmap.water_buffalo, ":water_buffalo:", ":water_buffalo:"), + Nature54(2, 1, R.mipmap.tiger2, ":tiger2:", ":tiger2:"), + Nature55(2, 1, R.mipmap.rabbit2, ":rabbit2:", ":rabbit2:"), + Nature56(2, 1, R.mipmap.dragon, ":dragon:", ":dragon:"), + Nature57(2, 1, R.mipmap.goat, ":goat:", ":goat:"), + Nature58(2, 1, R.mipmap.rooster, ":rooster:", ":rooster:"), + Nature59(2, 1, R.mipmap.dog2, ":dog2:", ":dog2:"), + Nature60(2, 1, R.mipmap.pig2, ":pig2:", ":pig2:"), + Nature61(2, 1, R.mipmap.mouse2, ":mouse2:", ":mouse2:"), + Nature62(2, 1, R.mipmap.ox, ":ox:", ":ox:"), + Nature63(2, 1, R.mipmap.dragon_face, ":dragon_face:", ":dragon_face:"), + Nature64(2, 1, R.mipmap.blowfish, ":blowfish:", ":blowfish:"), + Nature65(2, 1, R.mipmap.crocodile, ":crocodile:", ":crocodile:"), + Nature66(2, 1, R.mipmap.dromedary_camel, ":dromedary_camel:", ":dromedary_camel:"), + Nature67(2, 1, R.mipmap.leopard, ":leopard:", ":leopard:"), + Nature68(2, 1, R.mipmap.cat2, ":cat2:", ":cat2:"), + Nature69(2, 1, R.mipmap.poodle, ":poodle:", ":poodle:"), + Nature70(2, 1, R.mipmap.paw_prints, ":paw_prints:", ":paw_prints:"), + Nature71(2, 1, R.mipmap.bouquet, ":bouquet:", ":bouquet:"), + Nature72(2, 1, R.mipmap.cherry_blossom, ":cherry_blossom:", ":cherry_blossom:"), + Nature73(2, 1, R.mipmap.tulip, ":tulip:", ":tulip:"), + Nature74(2, 1, R.mipmap.four_leaf_clover, ":four_leaf_clover:", ":four_leaf_clover:"), + Nature75(2, 1, R.mipmap.rose, ":rose:", ":rose:"), + Nature76(2, 1, R.mipmap.sunflower, ":sunflower:", ":sunflower:"), + Nature77(2, 1, R.mipmap.hibiscus, ":hibiscus:", ":hibiscus:"), + Nature78(2, 1, R.mipmap.maple_leaf, ":maple_leaf:", ":maple_leaf:"), + Nature79(2, 1, R.mipmap.leaves, ":leaves:", ":leaves:"), + Nature80(2, 1, R.mipmap.fallen_leaf, ":fallen_leaf:", ":fallen_leaf:"), + Nature81(2, 1, R.mipmap.herb, ":herb:", ":herb:"), + Nature82(2, 1, R.mipmap.mushroom, ":mushroom:", ":mushroom:"), + Nature83(2, 1, R.mipmap.cactus, ":cactus:", ":cactus:"), + Nature84(2, 1, R.mipmap.palm_tree, ":palm_tree:", ":palm_tree:"), + Nature85(2, 1, R.mipmap.evergreen_tree, ":evergreen_tree:", ":evergreen_tree:"), + Nature86(2, 1, R.mipmap.deciduous_tree, ":deciduous_tree:", ":deciduous_tree:"), + Nature87(2, 1, R.mipmap.chestnut, ":chestnut:", ":chestnut:"), + Nature88(2, 1, R.mipmap.seedling, ":seedling:", ":seedling:"), + Nature89(2, 1, R.mipmap.blossom, ":blossom:", ":blossom:"), + Nature90(2, 1, R.mipmap.ear_of_rice, ":ear_of_rice:", ":ear_of_rice:"), + Nature91(2, 1, R.mipmap.shell, ":shell:", ":shell:"), + Nature92(2, 1, R.mipmap.globe_with_meridians, ":globe_with_meridians:", ":globe_with_meridians:"), + Nature93(2, 1, R.mipmap.sun_with_face, ":sun_with_face:", ":sun_with_face:"), + Nature94(2, 1, R.mipmap.full_moon_with_face, ":full_moon_with_face:", ":full_moon_with_face:"), + Nature95(2, 1, R.mipmap.new_moon_with_face, ":new_moon_with_face:", ":new_moon_with_face:"), + Nature96(2, 1, R.mipmap.new_moon, ":new_moon:", ":new_moon:"), + Nature97(2, 1, R.mipmap.waxing_crescent_moon, ":waxing_crescent_moon:", ":waxing_crescent_moon:"), + Nature98(2, 1, R.mipmap.first_quarter_moon, ":first_quarter_moon:", ":first_quarter_moon:"), + Nature99(2, 1, R.mipmap.waxing_gibbous_moon, ":waxing_gibbous_moon:", ":waxing_gibbous_moon:"), + Nature100(2, 1, R.mipmap.full_moon, ":full_moon:", ":full_moon:"), + Nature101(2, 1, R.mipmap.waning_gibbous_moon, ":waning_gibbous_moon:", ":waning_gibbous_moon:"), + Nature102(2, 1, R.mipmap.last_quarter_moon, ":last_quarter_moon:", ":last_quarter_moon:"), + Nature103(2, 1, R.mipmap.waning_crescent_moon, ":waning_crescent_moon:", ":waning_crescent_moon:"), + Nature104(2, 1, R.mipmap.last_quarter_moon_with_face, ":last_quarter_moon_with_face:", ":last_quarter_moon_with_face:"), + Nature105(2, 1, R.mipmap.first_quarter_moon_with_face, ":first_quarter_moon_with_face:", ":first_quarter_moon_with_face:"), + Nature106(2, 1, R.mipmap.moon, ":moon:", ":moon:"), + Nature107(2, 1, R.mipmap.earth_africa, ":earth_africa:", ":earth_africa:"), + Nature108(2, 1, R.mipmap.earth_americas, ":earth_americas:", ":earth_americas:"), + Nature109(2, 1, R.mipmap.earth_asia, ":earth_asia:", ":earth_asia:"), + Nature110(2, 1, R.mipmap.volcano, ":volcano:", ":volcano:"), + Nature111(2, 1, R.mipmap.milky_way, ":milky_way:", ":milky_way:"), + Nature112(2, 1, R.mipmap.partly_sunny, ":partly_sunny:", ":partly_sunny:"), + Nature113(2, 1, R.mipmap.octocat, ":octocat:", ":octocat:"), + Nature114(2, 1, R.mipmap.squirrel, ":squirrel:", ":squirrel:"); + + /********************************* + * 操作 + **************************************/ private String emojiStr; private String remote; private int value; @@ -816,8 +457,8 @@ public enum DisplayRules { private int type; private static Map sEmojiMap; - private DisplayRules(int type, int value, int resId, String cls, - String remote) { + DisplayRules(int type, int value, int resId, String cls, + String remote) { this.type = type; this.emojiStr = cls; this.value = value; diff --git a/app/src/main/java/net/oschina/app/emoji/EmojiGridAdapter.java b/app/src/main/java/net/oschina/app/emoji/EmojiGridAdapter.java index eadf9717f00084489375ee986182d68f6a8a2226..c4ff86a6b28a098deea32c227eabceaeef4d3910 100644 --- a/app/src/main/java/net/oschina/app/emoji/EmojiGridAdapter.java +++ b/app/src/main/java/net/oschina/app/emoji/EmojiGridAdapter.java @@ -15,10 +15,6 @@ */ package net.oschina.app.emoji; -import java.util.ArrayList; -import java.util.List; - -import net.oschina.app.R; import android.content.Context; import android.view.View; import android.view.ViewGroup; @@ -26,11 +22,15 @@ import android.widget.AbsListView.LayoutParams; import android.widget.BaseAdapter; import android.widget.ImageView; +import net.oschina.app.R; + +import java.util.ArrayList; +import java.util.List; + /** * 表情适配器 - * + * * @author kymjs (http://www.kymjs.com) - * */ public class EmojiGridAdapter extends BaseAdapter { diff --git a/app/src/main/java/net/oschina/app/emoji/EmojiKeyboardFragment.java b/app/src/main/java/net/oschina/app/emoji/EmojiKeyboardFragment.java index 00154dc16ceaf3f6d75cf23d1fd72501dc6314ba..fbc798830bdffb9446c89bcc61bf917b848d3f3b 100644 --- a/app/src/main/java/net/oschina/app/emoji/EmojiKeyboardFragment.java +++ b/app/src/main/java/net/oschina/app/emoji/EmojiKeyboardFragment.java @@ -1,8 +1,5 @@ package net.oschina.app.emoji; -import net.oschina.app.R; -import net.oschina.app.emoji.SoftKeyboardStateHelper.SoftKeyboardStateListener; -import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; @@ -11,43 +8,58 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; import android.widget.LinearLayout; -import android.widget.RadioGroup; +import net.oschina.app.R; +import net.oschina.app.emoji.SoftKeyboardStateHelper.SoftKeyboardStateListener; +import net.oschina.app.improve.widget.TitleBar; +import net.oschina.app.util.TDevice; + +/** + * 表情选择界面 + */ public class EmojiKeyboardFragment extends Fragment implements SoftKeyboardStateListener { - private LinearLayout mEmojiContent; - private RadioGroup mEmojiBottom; - private View[] mEmojiTabs; private ViewPager mEmojiPager; - - private EmojiPagerAdapter adapter; - private LinearLayout mRootView; private OnEmojiClickListener listener; - public static int EMOJI_TAB_CONTENT; + public int EMOJI_TAB_CONTENT; - private SoftKeyboardStateHelper mKeyboardHelper; + private boolean isDelegate = false; + private int keyboardHeightInPx = -1; + private int keyboardMinHeightInPx; + private int keyboardMaxHeightInPx; + private boolean isClipStatusHeight = false; @Override public View onCreateView(LayoutInflater inflater, - @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); - mRootView = (LinearLayout) inflater.inflate(R.layout.frag_keyboard, - container, false); - initWidget(mRootView); + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + if (null != mRootView) { + ViewGroup parent = (ViewGroup) mRootView.getParent(); + if (null != parent) { + parent.removeView(mRootView); + } + } else { + mRootView = (LinearLayout) inflater.inflate(R.layout.frag_keyboard, container, false); + initWidget(mRootView); + } + + intKeyboardHeight(); return mRootView; } + private void intKeyboardHeight() { + // init keyboard min and max height + keyboardMinHeightInPx = (int) TDevice.dipToPx(getResources(), isClipStatusHeight ? 176 : 151); + keyboardMaxHeightInPx = (int) TDevice.dipToPx(getResources(), 254); + } + private void initWidget(View rootView) { // bottom - mEmojiBottom = (RadioGroup) rootView.findViewById(R.id.emoji_bottom); - mEmojiBottom.setVisibility(View.VISIBLE); + ViewGroup mEmojiBottom = (ViewGroup) rootView.findViewById(R.id.emoji_bottom); EMOJI_TAB_CONTENT = mEmojiBottom.getChildCount() - 1; // 减一是因为有一个删除按钮 - mEmojiTabs = new View[EMOJI_TAB_CONTENT]; + View[] mEmojiTabs = new View[EMOJI_TAB_CONTENT]; if (EMOJI_TAB_CONTENT <= 1) { // 只有一个分类的时候就不显示了 mEmojiBottom.setVisibility(View.GONE); } @@ -55,7 +67,7 @@ public class EmojiKeyboardFragment extends Fragment implements mEmojiTabs[i] = mEmojiBottom.getChildAt(i); mEmojiTabs[i].setOnClickListener(getBottomBarClickListener(i)); } - mEmojiBottom.findViewById(R.id.emoji_bottom_del).setOnClickListener( + mEmojiBottom.findViewById(R.id.btn_del).setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { @@ -66,24 +78,30 @@ public class EmojiKeyboardFragment extends Fragment implements }); // content必须放在bottom下面初始化 - mEmojiContent = (LinearLayout) rootView - .findViewById(R.id.emoji_content); - mEmojiPager = (ViewPager) mEmojiContent.findViewById(R.id.emoji_pager); - adapter = new EmojiPagerAdapter(getFragmentManager(), - EMOJI_TAB_CONTENT, listener); + mEmojiPager = (ViewPager) rootView.findViewById(R.id.emoji_pager); + EmojiPagerAdapter adapter = new EmojiPagerAdapter(getChildFragmentManager(), EMOJI_TAB_CONTENT, new OnEmojiClickListener() { + @Override + public void onDeleteButtonClick(View v) { + if (listener != null) { + listener.onDeleteButtonClick(v); + } + } + + @Override + public void onEmojiClick(Emojicon v) { + if (listener != null) { + listener.onEmojiClick(v); + } + } + }); mEmojiPager.setAdapter(adapter); - mEmojiContent.setVisibility(View.VISIBLE); - mKeyboardHelper = new SoftKeyboardStateHelper(getActivity().getWindow() - .getDecorView()); + SoftKeyboardStateHelper mKeyboardHelper = new SoftKeyboardStateHelper(getActivity().getWindow().getDecorView()); mKeyboardHelper.addSoftKeyboardStateListener(this); } /** * 底部栏点击事件监听器 - * - * @param indexfff - * @return */ private OnClickListener getBottomBarClickListener(final int index) { return new OnClickListener() { @@ -98,49 +116,36 @@ public class EmojiKeyboardFragment extends Fragment implements this.listener = l; } - public void hideAllKeyBoard() { - hideEmojiKeyBoard(); - hideSoftKeyboard(); - } - public boolean isShow() { - return mEmojiContent.getVisibility() == View.VISIBLE; + return mRootView.getVisibility() == View.VISIBLE; } /** * 隐藏Emoji并显示软键盘 */ public void hideEmojiKeyBoard() { - mEmojiBottom.setVisibility(View.GONE); - mEmojiContent.setVisibility(View.GONE); + if (!isDelegate) { + mRootView.setVisibility(View.GONE); + } } /** * 显示Emoji并隐藏软键盘 */ public void showEmojiKeyBoard() { - mEmojiContent.setVisibility(View.VISIBLE); - if (EMOJI_TAB_CONTENT > 1) { - mEmojiBottom.setVisibility(View.VISIBLE); - } - } - - /** - * 隐藏软键盘 - */ - public void hideSoftKeyboard() { - ((InputMethodManager) getActivity().getSystemService( - Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow( - mEmojiBottom.getWindowToken(), 0); - } - - /** - * 显示软键盘 - */ - public void showSoftKeyboard(EditText et) { - ((InputMethodManager) getActivity().getSystemService( - Context.INPUT_METHOD_SERVICE)).showSoftInput(et, - InputMethodManager.SHOW_FORCED); + int height = keyboardHeightInPx; + height = Math.max(height, keyboardMinHeightInPx); + height = isClipStatusHeight + ? height - TitleBar.getExtPaddingTop(mRootView.getResources()) + : height; + if (height > 0) + height = Math.min(height, keyboardMaxHeightInPx); + + ViewGroup.LayoutParams params = mRootView.getLayoutParams(); + params.height = height; + mRootView.requestLayout(); + + mRootView.setVisibility(View.VISIBLE); } /** @@ -148,16 +153,20 @@ public class EmojiKeyboardFragment extends Fragment implements */ @Override public void onSoftKeyboardOpened(int keyboardHeightInPx) { - mEmojiBottom.setVisibility(View.GONE); - mEmojiContent.setVisibility(View.GONE); + this.keyboardHeightInPx = keyboardHeightInPx; + hideEmojiKeyBoard(); } @Override - public void onSoftKeyboardClosed() {} + public void onSoftKeyboardClosed() { + } - @Override - public void onStop() { - super.onStop(); - hideSoftKeyboard(); + public void setClipStatusHeight(boolean clipStatusHeight) { + isClipStatusHeight = clipStatusHeight; + intKeyboardHeight(); + } + + public void setDelegate(boolean delegate) { + isDelegate = delegate; } } diff --git a/app/src/main/java/net/oschina/app/emoji/EmojiPageFragment.java b/app/src/main/java/net/oschina/app/emoji/EmojiPageFragment.java index c15a2be694d40596853246487f3ba6e38a963dbc..1f8b710180e9633896919020e34f39bc2055582f 100644 --- a/app/src/main/java/net/oschina/app/emoji/EmojiPageFragment.java +++ b/app/src/main/java/net/oschina/app/emoji/EmojiPageFragment.java @@ -16,7 +16,6 @@ package net.oschina.app.emoji; import android.annotation.SuppressLint; -import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; @@ -34,9 +33,8 @@ import java.util.List; /** * 表情页,每页的显示 - * + * * @author kymjs (http://www.kymjs.com) - * */ @SuppressLint("ValidFragment") public class EmojiPageFragment extends Fragment { @@ -76,18 +74,19 @@ public class EmojiPageFragment extends Fragment { sGrid.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, - int position, long id) { + int position, long id) { EditText editText = (EditText) getActivity().findViewById( R.id.emoji_titile_input); if (listener != null) { listener.onEmojiClick((Emojicon) parent.getAdapter() .getItem(position)); } - InputHelper.input2OSC(editText, (Emojicon) parent.getAdapter() - .getItem(position)); + if (editText != null) + InputHelper.input2OSC(editText, (Emojicon) parent.getAdapter() + .getItem(position)); } }); - sGrid.setSelector(new ColorDrawable(android.R.color.transparent)); + sGrid.setSelector(R.drawable.ic_material); return sGrid; } diff --git a/app/src/main/java/net/oschina/app/emoji/EmojiPagerAdapter.java b/app/src/main/java/net/oschina/app/emoji/EmojiPagerAdapter.java index 636a012498f4d788cdac497203415e6deac655f6..0ba8b386ebdf56015b11edc389d4970d522ec27c 100644 --- a/app/src/main/java/net/oschina/app/emoji/EmojiPagerAdapter.java +++ b/app/src/main/java/net/oschina/app/emoji/EmojiPagerAdapter.java @@ -20,16 +20,15 @@ import android.support.v4.app.FragmentPagerAdapter; /** * 表情页适配器(FragmentPagerAdapter的好处是fragment常驻内存,对于要求效率而页卡很少的表情控件最合适) - * + * * @author kymjs (http://www.kymjs.com) - * */ public class EmojiPagerAdapter extends FragmentPagerAdapter { private OnEmojiClickListener listener; public EmojiPagerAdapter(FragmentManager fm, int tabCount, - OnEmojiClickListener l) { + OnEmojiClickListener l) { super(fm); KJEmojiFragment.EMOJI_TAB_CONTENT = tabCount; listener = l; diff --git a/app/src/main/java/net/oschina/app/emoji/Emojicon.java b/app/src/main/java/net/oschina/app/emoji/Emojicon.java index c82520ffac272f8570f86d4b9178c2da660ec415..b94ab7abeb6163ebfb82241fb556f109ec854865 100644 --- a/app/src/main/java/net/oschina/app/emoji/Emojicon.java +++ b/app/src/main/java/net/oschina/app/emoji/Emojicon.java @@ -16,7 +16,6 @@ package net.oschina.app.emoji; /** - * * @author kymjs (http://www.kymjs.com) */ public class Emojicon { diff --git a/app/src/main/java/net/oschina/app/emoji/InputHelper.java b/app/src/main/java/net/oschina/app/emoji/InputHelper.java index 786c6fe24115606292d56df763186b03aadc9881..18efad9054a1a322a50c1f181bdcea8b9e70f474 100644 --- a/app/src/main/java/net/oschina/app/emoji/InputHelper.java +++ b/app/src/main/java/net/oschina/app/emoji/InputHelper.java @@ -19,16 +19,25 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.text.Spannable; import android.text.SpannableString; +import android.text.TextUtils; import android.text.style.ImageSpan; import android.view.KeyEvent; import android.widget.EditText; -import net.oschina.app.R; +import net.oschina.app.util.TDevice; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** - * @author kymjs (http://www.kymjs.com) + * Emoji 表情解析类 */ public class InputHelper { + /** + * 删除Emoji表情 + * + * @param editText + */ public static void backspace(EditText editText) { if (editText == null) { return; @@ -55,60 +64,51 @@ public class InputHelper { * (I'm drunk, I go home) */ public static Spannable displayEmoji(Resources res, CharSequence s) { - String str = s.toString(); - Spannable spannable; - if (s instanceof Spannable) { - spannable = (Spannable) s; - } else { - // 构建文字span - spannable = new SpannableString(str); - } + return displayEmoji(res, s, (int) TDevice.spToPx(res, 20)); + } + + public static Spannable displayEmoji(Resources res, CharSequence s, int size) { + return displayEmoji(res, new SpannableString(s), size); + } + + public static Spannable displayEmoji(Resources res, Spannable spannable) { + return displayEmoji(res, spannable, (int) TDevice.spToPx(res, 20)); + } + + public static Spannable displayEmoji(Resources res, Spannable spannable, int size) { + String str = spannable.toString(); + if (!str.contains(":") && !str.contains("[")) { return spannable; } - for (int i = 0; i < str.length(); i++) { - int index1 = str.indexOf("[", i); - int length1 = str.indexOf("]", index1 + 1); - int index2 = str.indexOf(":", i); - int length2 = str.indexOf(":", index2 + 1); - int bound = (int) res.getDimension(R.dimen.space_20); + if (size == 0) + size = (int) TDevice.spToPx(res, 20); - try { - if (index1 > 0) { - String emojiStr = str.substring(index1, length1 + "]".length()); - int resId = getEmojiResId(emojiStr); - if (resId > 0) { - // 构建图片span - Drawable drawable = res.getDrawable(resId); + Pattern pattern = Pattern.compile("(\\[[^\\[\\]:\\s\\n]+\\])|(:[^:\\[\\]\\s\\n]+:)"); + Matcher matcher = pattern.matcher(str); + while (matcher.find()) { + String emojiStr = matcher.group(); + if (TextUtils.isEmpty(emojiStr)) continue; + int resId = getEmojiResId(emojiStr); + if (resId <= 0) continue; + Drawable drawable = res.getDrawable(resId); + if (drawable == null) continue; + drawable.setBounds(0, 0, size, size); - drawable.setBounds(0, 20, bound, bound + 20); - ImageSpan span = new ImageSpan(drawable, - ImageSpan.ALIGN_BASELINE); - spannable.setSpan(span, index1, length1 + "]".length(), - Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - } - } - if (index2 > 0) { - String emojiStr2 = str - .substring(index2, length2 + ":".length()); - int resId2 = getEmojiResId(emojiStr2); - if (resId2 > 0) { - Drawable emojiDrawable = res.getDrawable(resId2); - emojiDrawable.setBounds(0, 0, bound, bound); - // 构建图片span - ImageSpan imageSpan = new ImageSpan(emojiDrawable, str); - spannable.setSpan(imageSpan, index2, - length2 + ":".length(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - } catch (Exception e) { - } + ImageSpan span = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM); + spannable.setSpan(span, matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } + return spannable; } + /** + * 输入Emoji表情到 EditText + * + * @param editText EditText + * @param emojicon Emojicon + */ public static void input2OSC(EditText editText, Emojicon emojicon) { if (editText == null || emojicon == null) { return; @@ -118,11 +118,11 @@ public class InputHelper { if (start < 0) { // 没有多选时,直接在当前光标处添加 editText.append(displayEmoji(editText.getResources(), - emojicon.getRemote())); + emojicon.getRemote(), (int) editText.getTextSize())); } else { // 将已选中的部分替换为表情(当长按文字时会多选刷中很多文字) Spannable str = displayEmoji(editText.getResources(), - emojicon.getRemote()); + emojicon.getRemote(), (int) editText.getTextSize()); editText.getText().replace(Math.min(start, end), Math.max(start, end), str, 0, str.length()); } diff --git a/app/src/main/java/net/oschina/app/emoji/JSViewPager.java b/app/src/main/java/net/oschina/app/emoji/JSViewPager.java index b394e137c4a6b50638122ef8fc9162799e5c2a14..d14893f47fdb5a738f9e3a3b4bd8206e3c6165a6 100644 --- a/app/src/main/java/net/oschina/app/emoji/JSViewPager.java +++ b/app/src/main/java/net/oschina/app/emoji/JSViewPager.java @@ -22,9 +22,8 @@ import android.view.MotionEvent; /** * 重写ViewPager触屏操作,修正了系统ViewPager与Activity触摸屏事件冲突 - * + * * @author kymjs (http://www.kymjs.com) - * */ public class JSViewPager extends ViewPager { diff --git a/app/src/main/java/net/oschina/app/emoji/KJEmojiConfig.java b/app/src/main/java/net/oschina/app/emoji/KJEmojiConfig.java index 76b2abb4297619539500c9bf393f860b629b959c..712b91a05cb85bb54b6927b261443982308f7f78 100644 --- a/app/src/main/java/net/oschina/app/emoji/KJEmojiConfig.java +++ b/app/src/main/java/net/oschina/app/emoji/KJEmojiConfig.java @@ -18,7 +18,6 @@ package net.oschina.app.emoji; import net.oschina.app.R; /** - * * @author kymjs (http://www.kymjs.com) */ public class KJEmojiConfig { @@ -28,5 +27,5 @@ public class KJEmojiConfig { public static final int COUNT_IN_PAGE = 20; // 每页显示多少个表情(要减去一个删除符号:例如这里是三行七列) public static final int COLUMNS = 7; // 每页显示多少列 - public static final int DELETE_EMOJI_ID = R.drawable.btn_del; + public static final int DELETE_EMOJI_ID = R.mipmap.btn_del; } diff --git a/app/src/main/java/net/oschina/app/emoji/KJEmojiFragment.java b/app/src/main/java/net/oschina/app/emoji/KJEmojiFragment.java index edfd264145a6ab8021d81bd895dc867a258c58fd..d0915c3b383230d5dc1666ebb7f836347b513dda 100644 --- a/app/src/main/java/net/oschina/app/emoji/KJEmojiFragment.java +++ b/app/src/main/java/net/oschina/app/emoji/KJEmojiFragment.java @@ -31,30 +31,24 @@ import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.LinearLayout; -import android.widget.RadioGroup; import net.oschina.app.R; import net.oschina.app.emoji.SoftKeyboardStateHelper.SoftKeyboardStateListener; /** - * * @author kymjs (http://www.kymjs.com) - * */ public class KJEmojiFragment extends Fragment implements SoftKeyboardStateListener { private LinearLayout mRootView; - private View mEmojiTitle; - private LinearLayout mEmojiContent; - private RadioGroup mEmojiBottom; + private ViewGroup mEmojiBottom; + private ViewPager mEmojiContent; private View[] mEmojiTabs; private EditText mEt; private CheckBox mCBox; - private ViewPager mEmojiPager; - private EmojiPagerAdapter adapter; private OnSendClickListener listener; public static int EMOJI_TAB_CONTENT; @@ -64,11 +58,16 @@ public class KJEmojiFragment extends Fragment implements @Override public View onCreateView(LayoutInflater inflater, - @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); - mRootView = (LinearLayout) inflater.inflate(R.layout.frag_main, - container, false); - initWidget(mRootView); + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + if (null != mRootView) { + ViewGroup parent = (ViewGroup) mRootView.getParent(); + if (null != parent) { + parent.removeView(mRootView); + } + } else { + mRootView = (LinearLayout) inflater.inflate(R.layout.frag_main, container, false); + initWidget(mRootView); + } return mRootView; } @@ -94,7 +93,7 @@ public class KJEmojiFragment extends Fragment implements mCBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, - boolean isChecked) { + boolean isChecked) { if (isChecked) { showEmojiKeyBoard(); hideSoftKeyboard(); @@ -104,7 +103,7 @@ public class KJEmojiFragment extends Fragment implements } }); // bottom - mEmojiBottom = (RadioGroup) rootView.findViewById(R.id.emoji_bottom); + mEmojiBottom = (ViewGroup) rootView.findViewById(R.id.emoji_bottom); EMOJI_TAB_CONTENT = mEmojiBottom.getChildCount() - 1; // 减一是因为有一个删除按钮 mEmojiTabs = new View[EMOJI_TAB_CONTENT]; if (EMOJI_TAB_CONTENT <= 1) { // 只有一个分类的时候就不显示了 @@ -114,7 +113,7 @@ public class KJEmojiFragment extends Fragment implements mEmojiTabs[i] = mEmojiBottom.getChildAt(i); mEmojiTabs[i].setOnClickListener(getBottomBarClickListener(i)); } - mEmojiBottom.findViewById(R.id.emoji_bottom_del).setOnClickListener( + mEmojiBottom.findViewById(R.id.btn_del).setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { @@ -123,11 +122,9 @@ public class KJEmojiFragment extends Fragment implements }); // content必须放在bottom下面初始化 - mEmojiContent = (LinearLayout) rootView - .findViewById(R.id.emoji_content); - mEmojiPager = (ViewPager) mEmojiContent.findViewById(R.id.emoji_pager); - adapter = new EmojiPagerAdapter(getFragmentManager()); - mEmojiPager.setAdapter(adapter); + mEmojiContent = (ViewPager) rootView.findViewById(R.id.emoji_pager); + EmojiPagerAdapter adapter = new EmojiPagerAdapter(getFragmentManager()); + mEmojiContent.setAdapter(adapter); mKeyboardHelper = new SoftKeyboardStateHelper(getActivity().getWindow() .getDecorView()); @@ -150,7 +147,7 @@ public class KJEmojiFragment extends Fragment implements /** * 底部栏点击事件监听器 - * + * * @param index * @return */ @@ -158,7 +155,7 @@ public class KJEmojiFragment extends Fragment implements return new OnClickListener() { @Override public void onClick(View v) { - mEmojiPager.setCurrentItem(index); + mEmojiContent.setCurrentItem(index); } }; } @@ -213,9 +210,9 @@ public class KJEmojiFragment extends Fragment implements */ public void showSoftKeyboard() { mEt.requestFocus(); - ((InputMethodManager) getActivity().getSystemService( - Context.INPUT_METHOD_SERVICE)).showSoftInput(mEt, - InputMethodManager.SHOW_FORCED); + ((InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE)) + .showSoftInput(mEt, InputMethodManager.SHOW_FORCED); } public View getEmojiTitle() { @@ -259,7 +256,8 @@ public class KJEmojiFragment extends Fragment implements } @Override - public void onSoftKeyboardClosed() {} + public void onSoftKeyboardClosed() { + } @Override public void onStop() { diff --git a/app/src/main/java/net/oschina/app/emoji/OnSendClickListener.java b/app/src/main/java/net/oschina/app/emoji/OnSendClickListener.java index 2cf1791fe6d7b473b6523f6bbd4c9097cf86dcb4..cc2d21ca44ead8e9b435270bf2617c48765b121e 100644 --- a/app/src/main/java/net/oschina/app/emoji/OnSendClickListener.java +++ b/app/src/main/java/net/oschina/app/emoji/OnSendClickListener.java @@ -18,7 +18,6 @@ package net.oschina.app.emoji; import android.text.Editable; /** - * * @author kymjs (http://www.kymjs.com) */ public interface OnSendClickListener { diff --git a/app/src/main/java/net/oschina/app/emoji/ScrollGridView.java b/app/src/main/java/net/oschina/app/emoji/ScrollGridView.java deleted file mode 100644 index 30686db45da6326f7b836abd20c4646e81b51ca4..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/emoji/ScrollGridView.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2015, 张涛. - * - * 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 net.oschina.app.emoji; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.GridView; - -/** - * - * @author kymjs (http://www.kymjs.com) - */ -public class ScrollGridView extends GridView { - - public ScrollGridView(Context context) { - super(context); - } - - public ScrollGridView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ScrollGridView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int height = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, - MeasureSpec.AT_MOST); - super.onMeasure(widthMeasureSpec, height); - } -} diff --git a/app/src/main/java/net/oschina/app/emoji/SoftKeyboardStateHelper.java b/app/src/main/java/net/oschina/app/emoji/SoftKeyboardStateHelper.java index 87dbf2cb7ef9afe94f287aea9a4a777b23d80a1b..4e0885292f0f041924d211b5008249080e5afad2 100644 --- a/app/src/main/java/net/oschina/app/emoji/SoftKeyboardStateHelper.java +++ b/app/src/main/java/net/oschina/app/emoji/SoftKeyboardStateHelper.java @@ -15,16 +15,15 @@ */ package net.oschina.app.emoji; -import java.util.LinkedList; -import java.util.List; - import android.graphics.Rect; import android.view.View; import android.view.ViewTreeObserver; +import java.util.LinkedList; +import java.util.List; + /** - * - * @author kymjs (http://www.kymjs.com) + * 软键盘弹出辅助类,用于计算软键盘高度和打开状态 */ public class SoftKeyboardStateHelper implements ViewTreeObserver.OnGlobalLayoutListener { @@ -45,7 +44,7 @@ public class SoftKeyboardStateHelper implements } public SoftKeyboardStateHelper(View activityRootView, - boolean isSoftKeyboardOpened) { + boolean isSoftKeyboardOpened) { this.activityRootView = activityRootView; this.isSoftKeyboardOpened = isSoftKeyboardOpened; activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(this); @@ -61,8 +60,8 @@ public class SoftKeyboardStateHelper implements final int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top); if (!isSoftKeyboardOpened && heightDiff > 100) { // if more than 100 - // pixels, its probably - // a keyboard... + // pixels, its probably + // a keyboard... isSoftKeyboardOpened = true; notifyOnSoftKeyboardOpened(heightDiff); } else if (isSoftKeyboardOpened && heightDiff < 100) { @@ -81,7 +80,7 @@ public class SoftKeyboardStateHelper implements /** * Default value is zero (0) - * + * * @return last saved keyboard height in px */ public int getLastSoftKeyboardHeightInPx() { diff --git a/app/src/main/java/net/oschina/app/emoji/ToolbarFragment.java b/app/src/main/java/net/oschina/app/emoji/ToolbarFragment.java index cdf1b5d69f33d527bf954505710e5f1de6ba3be5..e078c0255bf3bcbd85cd497ce01e42bfca8824b7 100644 --- a/app/src/main/java/net/oschina/app/emoji/ToolbarFragment.java +++ b/app/src/main/java/net/oschina/app/emoji/ToolbarFragment.java @@ -13,7 +13,7 @@ import net.oschina.app.base.BaseFragment; public class ToolbarFragment extends BaseFragment { public interface OnActionClickListener { - public void onActionClick(ToolAction action); + void onActionClick(ToolAction action); } public enum ToolAction { @@ -36,10 +36,16 @@ public class ToolbarFragment extends BaseFragment { @Override public View onCreateView(LayoutInflater inflater, - @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - mRootView = inflater.inflate(R.layout.fragment_detail_tool_bar, - container, false); - initView(mRootView); + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + if (null != mRootView) { + ViewGroup parent = (ViewGroup) mRootView.getParent(); + if (null != parent) { + parent.removeView(mRootView); + } + } else { + mRootView = inflater.inflate(R.layout.fragment_detail_tool_bar, container, false); + initView(mRootView); + } return mRootView; } @@ -121,5 +127,6 @@ public class ToolbarFragment extends BaseFragment { } @Override - public void initData() {} + public void initData() { + } } \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/fragment/AboutOSCFragment.java b/app/src/main/java/net/oschina/app/fragment/AboutOSCFragment.java index 892465ce3527b91a5a91630517e9b5dda5b8bef9..b841035648f2d65eb34f203007c84ef0e88b27d4 100644 --- a/app/src/main/java/net/oschina/app/fragment/AboutOSCFragment.java +++ b/app/src/main/java/net/oschina/app/fragment/AboutOSCFragment.java @@ -1,33 +1,36 @@ package net.oschina.app.fragment; -import net.oschina.app.R; -import net.oschina.app.base.BaseFragment; -import net.oschina.app.bean.SimpleBackPage; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.UpdateManager; +import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; + +import net.oschina.app.BuildConfig; +import net.oschina.app.R; +import net.oschina.app.Setting; +import net.oschina.app.base.BaseFragment; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.UIHelper; +import net.oschina.common.admin.Boss; + +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; +import butterknife.OnClick; public class AboutOSCFragment extends BaseFragment { - @InjectView(R.id.tv_version) - TextView mTvVersionStatus; - - @InjectView(R.id.tv_version_name) + @Bind(R.id.tv_version_name) TextView mTvVersionName; @Override public View onCreateView(LayoutInflater inflater, - @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_about, container, false); - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); initView(view); initData(); return view; @@ -35,64 +38,48 @@ public class AboutOSCFragment extends BaseFragment { @Override public void initView(View view) { - view.findViewById(R.id.rl_check_update).setOnClickListener(this); - view.findViewById(R.id.rl_feedback).setOnClickListener(this); - view.findViewById(R.id.rl_grade).setOnClickListener(this); - view.findViewById(R.id.rl_gitapp).setOnClickListener(this); + view.findViewById(R.id.tv_grade).setOnClickListener(this); view.findViewById(R.id.tv_oscsite).setOnClickListener(this); view.findViewById(R.id.tv_knowmore).setOnClickListener(this); } @Override public void initData() { - mTvVersionName.setText("V " + TDevice.getVersionName()); + mTvVersionName.setText(BuildConfig.VERSION_NAME); } @Override + @OnClick(R.id.img_portrait) public void onClick(View v) { final int id = v.getId(); switch (id) { - case R.id.rl_check_update: - onClickUpdate(); - break; - case R.id.rl_feedback: - showFeedBack(); - break; - case R.id.rl_grade: - TDevice.openAppInMarket(getActivity()); - break; - case R.id.rl_gitapp: - boolean res = TDevice.openAppActivity(getActivity(), - "net.oschina.gitapp", "net.oschina.gitapp.WelcomePage"); - - if (!res) { - if (!TDevice.isHaveMarket(getActivity())) { - UIHelper.openSysBrowser(getActivity(), - "http://git.oschina.net/appclient"); - } else { - TDevice.gotoMarket(getActivity(), "net.oschina.gitapp"); - } - } - break; - case R.id.tv_oscsite: - UIHelper.openBrowser(getActivity(), "https://www.oschina.net"); - break; - case R.id.tv_knowmore: - UIHelper.openBrowser(getActivity(), - "https://www.oschina.net/home/aboutosc"); - break; - default: - break; + case R.id.tv_grade: + openMarket(); + break; + case R.id.tv_oscsite: + UIHelper.openInternalBrowser(getActivity(), "https://www.oschina.net"); + break; + case R.id.tv_knowmore: + UIHelper.openInternalBrowser(getActivity(), + "https://www.oschina.net/home/aboutosc"); + break; + case R.id.img_portrait: + Boss.verifyApp(getContext()); + Setting.updateSystemConfigTimeStamp(getContext()); + break; + default: + break; } } - private void onClickUpdate() { - new UpdateManager(getActivity(), true).checkUpdate(); - } - - private void showFeedBack() { - // TDevice.sendEmail(getActivity(), "用户反馈-OSC Android客户端", "", - // "apposchina@163.com"); - UIHelper.showSimpleBack(getActivity(), SimpleBackPage.FEED_BACK); + private void openMarket() { + try { + Uri uri = Uri.parse("market://details?id=" + BuildConfig.APPLICATION_ID); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } catch (Exception e) { + SimplexToast.show(getActivity(), "请至少安装一个应用商店"); + } } } diff --git a/app/src/main/java/net/oschina/app/fragment/ActiveFragment.java b/app/src/main/java/net/oschina/app/fragment/ActiveFragment.java deleted file mode 100644 index 5657fc4b35c1d18561d1f196817e9f00fee4a247..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/ActiveFragment.java +++ /dev/null @@ -1,212 +0,0 @@ -package net.oschina.app.fragment; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemLongClickListener; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.adapter.ActiveAdapter; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.bean.Active; -import net.oschina.app.bean.ActiveList; -import net.oschina.app.bean.Constants; -import net.oschina.app.bean.Notice; -import net.oschina.app.service.NoticeUtils; -import net.oschina.app.ui.MainActivity; -import net.oschina.app.ui.empty.EmptyLayout; -import net.oschina.app.util.DialogHelp; -import net.oschina.app.util.HTMLUtil; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; -import net.oschina.app.viewpagerfragment.NoticeViewPagerFragment; - -import java.io.InputStream; -import java.io.Serializable; - -/** - * 动态fragment - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @author kymjs (https://github.com/kymjs) - * @created 2014年10月22日 下午3:35:43 - * - */ -public class ActiveFragment extends BaseListFragment implements - OnItemLongClickListener { - - protected static final String TAG = ActiveFragment.class.getSimpleName(); - private static final String CACHE_KEY_PREFIX = "active_list"; - private boolean mIsWatingLogin; // 还没登陆 - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (mErrorLayout != null) { - mIsWatingLogin = true; - mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); - mErrorLayout.setErrorMessage(getString(R.string.unlogin_tip)); - } - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - IntentFilter filter = new IntentFilter(Constants.INTENT_ACTION_LOGOUT); - getActivity().registerReceiver(mReceiver, filter); - } - - @Override - public void onDestroy() { - getActivity().unregisterReceiver(mReceiver); - super.onDestroy(); - } - - @Override - public void onResume() { - if (mIsWatingLogin) { - mCurrentPage = 0; - mState = STATE_REFRESH; - requestData(false); - } - refreshNotice(); - super.onResume(); - } - - /** - * 开始刷新请求 - */ - private void refreshNotice() { - Notice notice = MainActivity.mNotice; - if (notice == null) { - return; - } - if (notice.getAtmeCount() > 0 && mCatalog == ActiveList.CATALOG_ATME) { - onRefresh(); - } else if (notice.getReviewCount() > 0 - && mCatalog == ActiveList.CATALOG_COMMENT) { - onRefresh(); - } - } - - @Override - protected ActiveAdapter getListAdapter() { - return new ActiveAdapter(); - } - - @Override - protected String getCacheKeyPrefix() { - return new StringBuffer(CACHE_KEY_PREFIX + mCatalog).append( - AppContext.getInstance().getLoginUid()).toString(); - } - - @Override - protected ActiveList parseList(InputStream is) { - ActiveList list = XmlUtils.toBean(ActiveList.class, is); - return list; - } - - @Override - protected ActiveList readList(Serializable seri) { - return ((ActiveList) seri); - } - - @Override - public void initView(View view) { - if (mCatalog == ActiveList.CATALOG_LASTEST) { - setHasOptionsMenu(true); - } - super.initView(view); - mListView.setOnItemLongClickListener(this); - mListView.setOnItemClickListener(this); - mErrorLayout.setOnLayoutClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (AppContext.getInstance().isLogin()) { - mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); - requestData(false); - } else { - UIHelper.showLoginActivity(getActivity()); - } - } - }); - if (AppContext.getInstance().isLogin()) { - UIHelper.sendBroadcastForNotice(getActivity()); - } - } - - @Override - protected void requestData(boolean refresh) { - if (AppContext.getInstance().isLogin()) { - mIsWatingLogin = false; - super.requestData(refresh); - } else { - mIsWatingLogin = true; - mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); - mErrorLayout.setErrorMessage(getString(R.string.unlogin_tip)); - } - } - - @Override - protected void sendRequestData() { - OSChinaApi.getActiveList(AppContext.getInstance().getLoginUid(), - mCatalog, mCurrentPage, mHandler); - } - - @Override - protected void onRefreshNetworkSuccess() { - if (AppContext.getInstance().isLogin()) { - if (0 == NoticeViewPagerFragment.sCurrentPage) { - NoticeUtils.clearNotice(Notice.TYPE_ATME); - } else if (1 == NoticeViewPagerFragment.sCurrentPage - || NoticeViewPagerFragment.sShowCount[1] > 0) { // 如果当前显示的是评论页,则发送评论页已被查看的Http请求 - NoticeUtils.clearNotice(Notice.TYPE_COMMENT); - } else { - NoticeUtils.clearNotice(Notice.TYPE_ATME); - } - UIHelper.sendBroadcastForNotice(getActivity()); - } - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Active active = mAdapter.getItem(position); - if (active != null) - UIHelper.showActiveRedirect(view.getContext(), active); - } - - @Override - public boolean onItemLongClick(AdapterView parent, View view, - int position, long id) { - final Active active = mAdapter.getItem(position); - if (active == null) - return false; - String[] items = new String[] { getResources().getString(R.string.copy) }; - DialogHelp.getSelectDialog(getActivity(), items, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(active.getMessage())); - } - }).show(); - return true; - } - - @Override - protected long getAutoRefreshTime() { - // 最新动态,即是好友圈 - if (mCatalog == ActiveList.CATALOG_LASTEST) { - return 5 * 60; - } - return super.getAutoRefreshTime(); - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/BlogDetailFragment.java b/app/src/main/java/net/oschina/app/fragment/BlogDetailFragment.java deleted file mode 100644 index 49a31ac2711b163731458bfb39062255904854a1..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/BlogDetailFragment.java +++ /dev/null @@ -1,133 +0,0 @@ -package net.oschina.app.fragment; - -import android.text.Editable; -import android.text.TextUtils; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.CommonDetailFragment; -import net.oschina.app.bean.Blog; -import net.oschina.app.bean.BlogDetail; -import net.oschina.app.bean.CommentList; -import net.oschina.app.bean.FavoriteList; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.ThemeSwitchUtils; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.URLsUtils; -import net.oschina.app.util.XmlUtils; - -import java.io.InputStream; - -/** - * Created by 火蚁 on 15/5/25. - */ -public class BlogDetailFragment extends CommonDetailFragment { - @Override - protected String getCacheKey() { - return "blog_" + mId; - } - - @Override - protected void sendRequestDataForNet() { - OSChinaApi.getBlogDetail(mId, mDetailHeandler); - } - - @Override - protected Blog parseData(InputStream is) { - return XmlUtils.toBean(BlogDetail.class, is).getBlog(); - } - - @Override - protected String getWebViewBody(Blog detail) { - StringBuffer body = new StringBuffer(); - body.append(UIHelper.WEB_STYLE).append(UIHelper.WEB_LOAD_IMAGES); - body.append(ThemeSwitchUtils.getWebViewBodyString()); - // 添加title - body.append(String.format("

    %s
    ", mDetail.getTitle())); - // 添加作者和时间 - String time = StringUtils.friendly_time(mDetail.getPubDate()); - String author = String.format("%s", mDetail.getAuthorId(), mDetail.getAuthor()); - body.append(String.format("
    %s    %s
    ", author, time)); - // 添加图片点击放大支持 - body.append(UIHelper.setHtmlCotentSupportImagePreview(mDetail.getBody())); - // 封尾 - body.append(""); - return body.toString(); - } - - @Override - protected void showCommentView() { - if (mDetail != null) { - UIHelper.showBlogComment(getActivity(), mId, - mDetail.getAuthorId()); - } - } - - @Override - protected int getCommentType() { - return CommentList.CATALOG_MESSAGE; - } - - @Override - protected String getShareTitle() { - return mDetail.getTitle(); - } - - @Override - protected String getShareContent() { - return StringUtils.getSubString(0, 55, - getFilterHtmlBody(mDetail.getBody())); - } - - @Override - protected String getShareUrl() { - return String.format(URLsUtils.URL_MOBILE + "blog/%s", mId); - } - - @Override - protected int getFavoriteTargetType() { - return FavoriteList.TYPE_BLOG; - } - - @Override - protected int getFavoriteState() { - return mDetail.getFavorite(); - } - - @Override - protected void updateFavoriteChanged(int newFavoritedState) { - mDetail.setFavorite(newFavoritedState); - saveCache(mDetail); - } - - @Override - protected int getCommentCount() { - return mDetail.getCommentCount(); - } - - @Override - public void onClickSendButton(Editable str) { - if (!TDevice.hasInternet()) { - AppContext.showToastShort(R.string.tip_network_error); - return; - } - if (!AppContext.getInstance().isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - if (TextUtils.isEmpty(str)) { - AppContext.showToastShort(R.string.tip_comment_content_empty); - return; - } - showWaitDialog(R.string.progress_submit); - OSChinaApi.publicBlogComment(mId, AppContext.getInstance() - .getLoginUid(), str.toString(), mCommentHandler); - } - - @Override - protected String getRepotrUrl() { - return mDetail.getUrl(); - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/BlogFragment.java b/app/src/main/java/net/oschina/app/fragment/BlogFragment.java deleted file mode 100644 index ebd209f7497911bd6085455eefc687e723be15ae..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/BlogFragment.java +++ /dev/null @@ -1,98 +0,0 @@ -package net.oschina.app.fragment; - -import java.io.InputStream; -import java.io.Serializable; - -import net.oschina.app.adapter.BlogAdapter; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.bean.Blog; -import net.oschina.app.bean.BlogList; -import net.oschina.app.interf.OnTabReselectListener; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; - -/** - * 博客区中单一模块的展示 - * - * @author kymjs(kymjs123@gmail.com) - */ -public class BlogFragment extends BaseListFragment implements - OnTabReselectListener { - - public static final String BUNDLE_BLOG_TYPE = "BUNDLE_BLOG_TYPE"; - - protected static final String TAG = BlogFragment.class.getSimpleName(); - private static final String CACHE_KEY_PREFIX = "bloglist_"; - - private String blogType; - - @Override - protected BlogAdapter getListAdapter() { - return new BlogAdapter(); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Bundle args = getArguments(); - if (args != null) { - blogType = args.getString(BUNDLE_BLOG_TYPE); - } - } - - /** - * 获取当前展示页面的缓存数据 - */ - @Override - protected String getCacheKeyPrefix() { - return CACHE_KEY_PREFIX + blogType; - } - - @Override - protected BlogList parseList(InputStream is) throws Exception { - BlogList list = XmlUtils.toBean(BlogList.class, is); - return list; - } - - @Override - protected BlogList readList(Serializable seri) { - return ((BlogList) seri); - } - - @Override - protected void sendRequestData() { - OSChinaApi.getBlogList(blogType, mCurrentPage, mHandler); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Blog blog = mAdapter.getItem(position); - if (blog != null) { - UIHelper.showBlogDetail(getActivity(), blog.getId(), - blog.getCommentCount()); - // 保存到已读列表 - saveToReadedList(view, BlogList.PREF_READED_BLOG_LIST, blog.getId() - + ""); - } - } - - @Override - public void onTabReselect() { - onRefresh(); - } - - @Override - protected long getAutoRefreshTime() { - // TODO Auto-generated method stub - // 最新博客 - if (blogType.equals(BlogList.CATALOG_LATEST)) { - return 2 * 60 * 60; - } - return super.getAutoRefreshTime(); - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/BrowserFragment.java b/app/src/main/java/net/oschina/app/fragment/BrowserFragment.java index 4fe858e6f11ba70ea852fece339b374802f12e49..7b40577776b6f6169d22cff4970cf4e5caaf9683 100644 --- a/app/src/main/java/net/oschina/app/fragment/BrowserFragment.java +++ b/app/src/main/java/net/oschina/app/fragment/BrowserFragment.java @@ -6,6 +6,7 @@ import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; +import android.support.v7.app.AlertDialog; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.LayoutInflater; @@ -29,37 +30,38 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; -import net.oschina.app.AppConfig; import net.oschina.app.AppContext; import net.oschina.app.R; import net.oschina.app.base.BaseActivity; import net.oschina.app.base.BaseFragment; -import net.oschina.app.ui.ShareDialog; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.dialog.ShareDialogBuilder; import net.oschina.app.ui.SimpleBackActivity; +import net.oschina.app.util.TDevice; +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; /** * 浏览器界面 - * + * * @author kymjs(kymjs123@gmail.com) */ @SuppressLint("NewApi") public class BrowserFragment extends BaseFragment { - @InjectView(R.id.webview) + @Bind(R.id.webview) WebView mWebView; - @InjectView(R.id.browser_back) + @Bind(R.id.browser_back) ImageView mImgBack; - @InjectView(R.id.browser_forward) + @Bind(R.id.browser_forward) ImageView mImgForward; - @InjectView(R.id.browser_refresh) + @Bind(R.id.browser_refresh) ImageView mImgRefresh; - @InjectView(R.id.browser_system_browser) + @Bind(R.id.browser_system_browser) ImageView mImgSystemBrowser; - @InjectView(R.id.browser_bottom) + @Bind(R.id.browser_bottom) LinearLayout mLayoutBottom; - @InjectView(R.id.progress) + @Bind(R.id.progress) ProgressBar mProgress; public static final String BROWSER_KEY = "browser_url"; @@ -72,29 +74,31 @@ public class BrowserFragment extends BaseFragment { private Animation animBottomIn, animBottomOut; private GestureDetector mGestureDetector; private CookieManager cookie; + private ShareDialogBuilder mShareDialogBuilder; + private AlertDialog alertDialog; @Override public void onClick(View v) { switch (v.getId()) { - case R.id.browser_back: - mWebView.goBack(); - break; - case R.id.browser_forward: - mWebView.goForward(); - break; - case R.id.browser_refresh: - mWebView.loadUrl(mWebView.getUrl()); - break; - case R.id.browser_system_browser: - try { - // 启用外部浏览器 - Uri uri = Uri.parse(mCurrentUrl); - Intent it = new Intent(Intent.ACTION_VIEW, uri); - aty.startActivity(it); - } catch (Exception e) { - AppContext.showToast("网页地址错误"); - } - break; + case R.id.browser_back: + mWebView.goBack(); + break; + case R.id.browser_forward: + mWebView.goForward(); + break; + case R.id.browser_refresh: + mWebView.loadUrl(mWebView.getUrl()); + break; + case R.id.browser_system_browser: + try { + // 启用外部浏览器 + Uri uri = Uri.parse(mCurrentUrl); + Intent it = new Intent(Intent.ACTION_VIEW, uri); + aty.startActivity(it); + } catch (Exception e) { + AppContext.showToast("网页地址错误"); + } + break; } } @@ -121,6 +125,11 @@ public class BrowserFragment extends BaseFragment { public void onResume() { super.onResume(); mWebView.onResume(); + + if (mShareDialogBuilder != null) { + mShareDialogBuilder.cancelLoading(); + } + } @Override @@ -132,7 +141,8 @@ public class BrowserFragment extends BaseFragment { @Override public void onDestroy() { super.onDestroy(); - mWebView.destroy(); + if (mWebView != null) + mWebView.destroy(); } @Override @@ -146,15 +156,20 @@ public class BrowserFragment extends BaseFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); - View rootView = inflater.inflate(R.layout.fragment_browser, container, - false); - aty = getActivity(); - ButterKnife.inject(this, rootView); - initData(); - initView(rootView); - return rootView; + Bundle savedInstanceState) { + if (TDevice.hasWebView(getContext())) { + View rootView = inflater.inflate(R.layout.fragment_browser, container, + false); + aty = getActivity(); + ButterKnife.bind(this, rootView); + initData(); + initView(rootView); + return rootView; + } else { + getActivity().finish(); + return super.onCreateView(inflater, container, savedInstanceState); + } + } @Override @@ -171,9 +186,9 @@ public class BrowserFragment extends BaseFragment { @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.public_menu_shared: - showSharedDialog(); - break; + case R.id.public_menu_shared: + showSharedDialog(); + break; } return true; } @@ -187,10 +202,12 @@ public class BrowserFragment extends BaseFragment { R.anim.anim_bottom_out); animBottomIn.setAnimationListener(new AnimationListener() { @Override - public void onAnimationStart(Animation animation) {} + public void onAnimationStart(Animation animation) { + } @Override - public void onAnimationRepeat(Animation animation) {} + public void onAnimationRepeat(Animation animation) { + } @Override public void onAnimationEnd(Animation animation) { @@ -199,10 +216,12 @@ public class BrowserFragment extends BaseFragment { }); animBottomOut.setAnimationListener(new AnimationListener() { @Override - public void onAnimationStart(Animation animation) {} + public void onAnimationStart(Animation animation) { + } @Override - public void onAnimationRepeat(Animation animation) {} + public void onAnimationRepeat(Animation animation) { + } @Override public void onAnimationEnd(Animation animation) { @@ -215,35 +234,35 @@ public class BrowserFragment extends BaseFragment { * 打开分享dialog */ private void showSharedDialog() { - final ShareDialog dialog = new ShareDialog(getActivity()); - dialog.setCancelable(true); - dialog.setCanceledOnTouchOutside(true); - dialog.setTitle(R.string.share_to); - dialog.setShareInfo(getShareTitle(), getShareContent(), mCurrentUrl); - dialog.show(); + if (mShareDialogBuilder == null) { + mShareDialogBuilder = ShareDialogBuilder.with(getActivity()) + .title(getShareTitle()) + .content(getShareContent()) + .url(mCurrentUrl) + .build(); + } + if (alertDialog == null) + alertDialog = mShareDialogBuilder.create(); + alertDialog.show(); } + /** * 载入链接之前会被调用 - * - * @param view - * WebView - * @param url - * 链接地址 + * + * @param view WebView + * @param url 链接地址 */ protected void onUrlLoading(WebView view, String url) { mProgress.setVisibility(View.VISIBLE); - cookie.setCookie(url, - AppContext.getInstance().getProperty(AppConfig.CONF_COOKIE)); + cookie.setCookie(url, AccountHelper.getCookie()); } /** * 链接载入成功后会被调用 - * - * @param view - * WebView - * @param url - * 链接地址 + * + * @param view WebView + * @param url 链接地址 */ protected void onUrlFinished(WebView view, String url) { mCurrentUrl = url; @@ -252,11 +271,9 @@ public class BrowserFragment extends BaseFragment { /** * 当前WebView显示页面的标题 - * - * @param view - * WebView - * @param title - * web页面标题 + * + * @param view WebView + * @param title web页面标题 */ protected void onWebTitle(WebView view, String title) { if (aty != null && mWebView != null) { // 必须做判断,由于webview加载属于耗时操作,可能会本Activity已经关闭了才被调用 @@ -266,13 +283,12 @@ public class BrowserFragment extends BaseFragment { /** * 当前WebView显示页面的图标 - * - * @param view - * WebView - * @param icon - * web页面图标 + * + * @param view WebView + * @param icon web页面图标 */ - protected void onWebIcon(WebView view, Bitmap icon) {} + protected void onWebIcon(WebView view, Bitmap icon) { + } /** * 初始化浏览器设置信息 diff --git a/app/src/main/java/net/oschina/app/fragment/CommentFrament.java b/app/src/main/java/net/oschina/app/fragment/CommentFrament.java deleted file mode 100644 index 1667c7196c6dc00c8e27397b36bb1e6e0b1df0e7..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/CommentFrament.java +++ /dev/null @@ -1,337 +0,0 @@ -package net.oschina.app.fragment; - -import android.app.Activity; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemLongClickListener; - -import com.loopj.android.http.AsyncHttpResponseHandler; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.adapter.CommentAdapter; -import net.oschina.app.api.OperationResponseHandler; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseActivity; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.bean.BlogCommentList; -import net.oschina.app.bean.Comment; -import net.oschina.app.bean.CommentList; -import net.oschina.app.bean.ListEntity; -import net.oschina.app.bean.Result; -import net.oschina.app.bean.ResultBean; -import net.oschina.app.emoji.OnSendClickListener; -import net.oschina.app.ui.DetailActivity; -import net.oschina.app.util.DialogHelp; -import net.oschina.app.util.HTMLUtil; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; - -import cz.msebera.android.httpclient.Header; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.Serializable; - -public class CommentFrament extends BaseListFragment implements - OnItemLongClickListener, OnSendClickListener { - - public static final String BUNDLE_KEY_CATALOG = "BUNDLE_KEY_CATALOG"; - public static final String BUNDLE_KEY_BLOG = "BUNDLE_KEY_BLOG"; - public static final String BUNDLE_KEY_ID = "BUNDLE_KEY_ID"; - public static final String BUNDLE_KEY_OWNER_ID = "BUNDLE_KEY_OWNER_ID"; - protected static final String TAG = CommentFrament.class.getSimpleName(); - private static final String BLOG_CACHE_KEY_PREFIX = "blogcomment_list"; - private static final String CACHE_KEY_PREFIX = "comment_list"; - private static final int REQUEST_CODE = 0x10; - - private int mId, mOwnerId; - private boolean mIsBlogComment; - private DetailActivity outAty; - - private final AsyncHttpResponseHandler mCommentHandler = new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - try { - ResultBean rsb = XmlUtils.toBean(ResultBean.class, - new ByteArrayInputStream(arg2)); - Result res = rsb.getResult(); - if (res.OK()) { - hideWaitDialog(); - AppContext.showToastShort(R.string.comment_publish_success); - - mAdapter.addItem(0, rsb.getComment()); - mAdapter.notifyDataSetChanged(); - UIHelper.sendBroadCastCommentChanged(getActivity(), - mIsBlogComment, mId, mCatalog, Comment.OPT_ADD, - rsb.getComment()); - onRefresh(); - outAty.emojiFragment.clean(); - } else { - hideWaitDialog(); - AppContext.showToastShort(res.getErrorMessage()); - } - } catch (Exception e) { - e.printStackTrace(); - onFailure(arg0, arg1, arg2, e); - } - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) { - hideWaitDialog(); - AppContext.showToastShort(R.string.comment_publish_faile); - } - }; - - @Override - public void initView(View view) { - super.initView(view); - mListView.setOnItemLongClickListener(this); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - outAty = (DetailActivity) getActivity(); - return super.onCreateView(inflater, container, savedInstanceState); - } - - @Override - public void onCreate(android.os.Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Bundle args = getActivity().getIntent().getExtras(); - if (args != null) { - mCatalog = args.getInt(BUNDLE_KEY_CATALOG, 0); - mId = args.getInt(BUNDLE_KEY_ID, 0); - mOwnerId = args.getInt(BUNDLE_KEY_OWNER_ID, 0); - mIsBlogComment = args.getBoolean(BUNDLE_KEY_BLOG, false); - } - - if (!mIsBlogComment && mCatalog == CommentList.CATALOG_POST) { - ((BaseActivity) getActivity()) - .setActionBarTitle(R.string.post_answer); - } - - int mode = WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN - | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - getActivity().getWindow().setSoftInputMode(mode); - } - - @Override - public void onResume() { - super.onResume(); - outAty.emojiFragment.hideFlagButton(); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) { - Comment comment = data - .getParcelableExtra(Comment.BUNDLE_KEY_COMMENT); - if (comment != null) { - mAdapter.addItem(0, comment); - } - } - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - protected CommentAdapter getListAdapter() { - return new CommentAdapter(); - } - - @Override - protected String getCacheKeyPrefix() { - String str = mIsBlogComment ? BLOG_CACHE_KEY_PREFIX : CACHE_KEY_PREFIX; - return new StringBuilder(str).append("_").append(mId).append("_Owner") - .append(mOwnerId).toString(); - } - - @Override - protected ListEntity parseList(InputStream is) throws Exception { - if (mIsBlogComment) { - return XmlUtils.toBean(BlogCommentList.class, is); - } else { - return XmlUtils.toBean(CommentList.class, is); - } - } - - @Override - protected ListEntity readList(Serializable seri) { - if (mIsBlogComment) - return ((BlogCommentList) seri); - return ((CommentList) seri); - } - - @Override - protected void sendRequestData() { - if (mIsBlogComment) { - OSChinaApi.getBlogCommentList(mId, mCurrentPage, mHandler); - } else { - OSChinaApi.getCommentList(mId, mCatalog, mCurrentPage, mHandler); - } - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - final Comment comment = mAdapter.getItem(position); - if (comment == null) - return; - outAty.emojiFragment.getEditText().setTag(comment); - outAty.emojiFragment.getEditText().setHint("回复:" + comment.getAuthor()); - outAty.emojiFragment.showSoftKeyboard(); - } - - private void handleDeleteComment(Comment comment) { - if (!AppContext.getInstance().isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - AppContext.showToastShort(R.string.deleting); - if (mIsBlogComment) { - OSChinaApi.deleteBlogComment( - AppContext.getInstance().getLoginUid(), mId, - comment.getId(), comment.getAuthorId(), mOwnerId, - new DeleteOperationResponseHandler(comment)); - } else { - OSChinaApi - .deleteComment(mId, mCatalog, comment.getId(), - comment.getAuthorId(), - new DeleteOperationResponseHandler(comment)); - } - } - - class DeleteOperationResponseHandler extends OperationResponseHandler { - - DeleteOperationResponseHandler(Object... args) { - super(args); - } - - @Override - public void onSuccess(int code, ByteArrayInputStream is, Object[] args) { - try { - Result res = XmlUtils.toBean(ResultBean.class, is).getResult(); - if (res.OK()) { - AppContext.showToastShort(R.string.delete_success); - mAdapter.removeItem(args[0]); - } else { - AppContext.showToastShort(res.getErrorMessage()); - } - } catch (Exception e) { - e.printStackTrace(); - onFailure(code, e.getMessage(), args); - } - } - - @Override - public void onFailure(int code, String errorMessage, Object[] args) { - AppContext.showToastShort(R.string.delete_faile); - } - } - - @Override - public boolean onItemLongClick(AdapterView parent, View view, - int position, long id) { - final Comment item = mAdapter.getItem(position); - if (item == null) - return false; - int itemsLen = item.getAuthorId() == AppContext.getInstance() - .getLoginUid() ? 2 : 1; - String[] items = new String[itemsLen]; - items[0] = getResources().getString(R.string.copy); - if (itemsLen == 2) { - items[1] = getResources().getString(R.string.delete); - } - DialogHelp.getSelectDialog(getActivity(), items, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (i == 0) { - TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(item - .getContent())); - } else if (i == 1) { - handleDeleteComment(item); - } - } - }).show(); - return true; - } - - @Override - public void onClickSendButton(Editable text) { - if (!TDevice.hasInternet()) { - AppContext.showToastShort(R.string.tip_network_error); - return; - } - if (TextUtils.isEmpty(text)) { - AppContext.showToastShort(R.string.tip_comment_content_empty); - return; - } - if (!AppContext.getInstance().isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - if (outAty.emojiFragment.getEditText().getTag() != null) { - handleReplyComment((Comment) outAty.emojiFragment.getEditText().getTag(), - text.toString()); - } else { - sendReply(text.toString()); - } - } - - private void sendReply(String text) { - showWaitDialog(R.string.progress_submit); - if (mIsBlogComment) { - OSChinaApi.publicBlogComment(mId, AppContext.getInstance() - .getLoginUid(), text, mCommentHandler); - } else { - OSChinaApi.publicComment(mCatalog, mId, AppContext.getInstance() - .getLoginUid(), text, 1, mCommentHandler); - } - } - - private void handleReplyComment(Comment comment, String text) { - showWaitDialog(R.string.progress_submit); - if (!AppContext.getInstance().isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - if (mIsBlogComment) { - OSChinaApi.replyBlogComment(mId, AppContext.getInstance() - .getLoginUid(), text, comment.getId(), comment - .getAuthorId(), mCommentHandler); - } else { - OSChinaApi.replyComment(mId, mCatalog, comment.getId(), comment - .getAuthorId(), AppContext.getInstance().getLoginUid(), - text, mCommentHandler); - } - } - - @Override - public boolean onBackPressed() { - if (outAty.emojiFragment.isShowEmojiKeyBoard()) { - outAty.emojiFragment.hideAllKeyBoard(); - return true; - } - if (outAty.emojiFragment.getEditText().getTag() != null) { - outAty.emojiFragment.getEditText().setTag(null); - outAty.emojiFragment.getEditText().setHint("说点什么吧"); - return true; - } - return super.onBackPressed(); - } - - @Override - public void onClickFlagButton() {} -} diff --git a/app/src/main/java/net/oschina/app/fragment/EventAppliesFragment.java b/app/src/main/java/net/oschina/app/fragment/EventAppliesFragment.java index d7ce33c2b08d3e9ba12c419f89d9945afb8eb323..14e08c9ed4948d41e12681185cfaf62bc38eed02 100644 --- a/app/src/main/java/net/oschina/app/fragment/EventAppliesFragment.java +++ b/app/src/main/java/net/oschina/app/fragment/EventAppliesFragment.java @@ -1,76 +1,75 @@ package net.oschina.app.fragment; -import java.io.InputStream; -import java.io.Serializable; +import android.annotation.TargetApi; +import android.os.Build; +import android.view.View; +import android.widget.AdapterView; -import net.oschina.app.AppContext; import net.oschina.app.adapter.EventApplyAdapter; import net.oschina.app.api.remote.OSChinaApi; import net.oschina.app.base.BaseListFragment; import net.oschina.app.bean.Apply; import net.oschina.app.bean.EventAppliesList; +import net.oschina.app.improve.account.AccountHelper; import net.oschina.app.util.UIHelper; import net.oschina.app.util.XmlUtils; -import android.annotation.TargetApi; -import android.os.Build; -import android.view.View; -import android.widget.AdapterView; + +import java.io.InputStream; +import java.io.Serializable; /** * 活动出席人员列表 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年12月12日 下午7:59:10 - * */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class EventAppliesFragment extends BaseListFragment { - protected static final String TAG = EventAppliesFragment.class.getSimpleName(); - private static final String CACHE_KEY_PREFIX = "event_apply_user_list"; + protected static final String TAG = EventAppliesFragment.class.getSimpleName(); + private static final String CACHE_KEY_PREFIX = "event_apply_user_list"; + + @Override + public void initView(View view) { + super.initView(view); + } - @Override - public void initView(View view) { - super.initView(view); - } + @Override + protected EventApplyAdapter getListAdapter() { + return new EventApplyAdapter(); + } - @Override - protected EventApplyAdapter getListAdapter() { - return new EventApplyAdapter(); - } + @Override + protected String getCacheKeyPrefix() { + return CACHE_KEY_PREFIX + "_" + mCatalog; + } - @Override - protected String getCacheKeyPrefix() { - return CACHE_KEY_PREFIX + "_" + mCatalog; - } + @Override + protected EventAppliesList parseList(InputStream is) throws Exception { + return XmlUtils.toBean(EventAppliesList.class, is); + } - @Override - protected EventAppliesList parseList(InputStream is) throws Exception { - EventAppliesList list = XmlUtils.toBean(EventAppliesList.class, is); - return list; - } + @Override + protected EventAppliesList readList(Serializable seri) { + return ((EventAppliesList) seri); + } - @Override - protected EventAppliesList readList(Serializable seri) { - return ((EventAppliesList) seri); - } + @Override + protected void sendRequestData() { + OSChinaApi.getEventApplies(mCatalog, mCurrentPage, mHandler); + } - @Override - protected void sendRequestData() { - OSChinaApi.getEventApplies(mCatalog, mCurrentPage, mHandler); - } + @Override + public void onItemClick(AdapterView parent, View view, int position, + long id) { + Apply item = mAdapter.getItem(position); + if (item != null) { + if (AccountHelper.isLogin()) { + UIHelper.showMessageDetail(getActivity(), item.getId(), item.getName()); + return; + } + UIHelper.showUserCenter(getActivity(), item.getId(), item.getName()); + } - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Apply item = (Apply) mAdapter.getItem(position); - if (item != null) { - if (AppContext.getInstance().isLogin()) { - UIHelper.showMessageDetail(getActivity(), item.getId(), item.getName()); - return; - } - UIHelper.showUserCenter(getActivity(), item.getId(),item.getName()); - } - - } + } } diff --git a/app/src/main/java/net/oschina/app/fragment/EventDetailFragment.java b/app/src/main/java/net/oschina/app/fragment/EventDetailFragment.java deleted file mode 100644 index 60262d4bc1eb1da490fca42be34c4d301a2052d0..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/EventDetailFragment.java +++ /dev/null @@ -1,322 +0,0 @@ -package net.oschina.app.fragment; - -import android.content.DialogInterface; -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import com.loopj.android.http.AsyncHttpResponseHandler; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.base.CommonDetailFragment; -import net.oschina.app.bean.CommentList; -import net.oschina.app.bean.Event; -import net.oschina.app.bean.EventApplyData; -import net.oschina.app.bean.FavoriteList; -import net.oschina.app.bean.Post; -import net.oschina.app.bean.PostDetail; -import net.oschina.app.bean.Result; -import net.oschina.app.bean.ResultBean; -import net.oschina.app.bean.SimpleBackPage; -import net.oschina.app.ui.EventApplyDialog; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.ThemeSwitchUtils; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.URLsUtils; -import net.oschina.app.util.XmlUtils; - -import cz.msebera.android.httpclient.Header; -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -import butterknife.InjectView; - -/** - * Created by 火蚁 on 15/5/28. - */ -public class EventDetailFragment extends CommonDetailFragment { - - @InjectView(R.id.tv_event_title) - TextView mTvTitle; - - @InjectView(R.id.tv_event_start_time) - TextView mTvStartTime; - - @InjectView(R.id.tv_event_end_time) - TextView mTvEndTime; - - @InjectView(R.id.tv_event_spot) - TextView mTvSpot; - - @InjectView(R.id.rl_event_location) - View mLocation; - - @InjectView(R.id.bt_event_attend) - Button mBtAttend;// 出席人员 - - @InjectView(R.id.bt_event_apply) - Button mBtEventApply;// 活动报名 - - @InjectView(R.id.tv_event_tip) - TextView mEventTip; - - private EventApplyDialog mEventApplyDialog; - - @Override - public void initView(View view) { - super.initView(view); - mLocation.setOnClickListener(this); - mBtAttend.setOnClickListener(this); - mBtEventApply.setOnClickListener(this); - } - - @Override - protected int getLayoutId() { - return R.layout.fragment_event_detail; - } - - @Override - protected String getCacheKey() { - return "post_" + mId; - } - - @Override - protected void sendRequestDataForNet() { - OSChinaApi.getPostDetail(mId, mDetailHeandler); - } - - @Override - protected Post parseData(InputStream is) { - return XmlUtils.toBean(PostDetail.class, is).getPost(); - } - - @Override - protected String getWebViewBody(Post detail) { - StringBuffer body = new StringBuffer(); - body.append(UIHelper.WEB_STYLE).append(UIHelper.WEB_LOAD_IMAGES); - body.append(ThemeSwitchUtils.getWebViewBodyString()); - // 添加title - body.append(String.format("
    %s
    ", mDetail.getTitle())); - // 添加作者和时间 - String time = StringUtils.friendly_time(mDetail.getPubDate()); - String author = String.format("%s", mDetail.getAuthorId(), mDetail.getAuthor()); - body.append(String.format("
    %s    %s
    ", author, time)); - // 添加图片点击放大支持 - body.append(UIHelper.setHtmlCotentSupportImagePreview(mDetail.getBody())); - // 封尾 - body.append(""); - return body.toString(); - } - - @Override - protected void executeOnLoadDataSuccess(Post detail) { - super.executeOnLoadDataSuccess(detail); - mTvTitle.setText(mDetail.getTitle()); - mTvStartTime.setText(String.format( - getString(R.string.event_start_time), mDetail.getEvent() - .getStartTime())); - mTvEndTime.setText(String.format(getString(R.string.event_end_time), - mDetail.getEvent().getEndTime())); - mTvSpot.setText(mDetail.getEvent().getCity() + " " - + mDetail.getEvent().getSpot()); - - // 站外活动 - if (mDetail.getEvent().getCategory() == 4) { - mBtEventApply.setVisibility(View.VISIBLE); - mBtAttend.setVisibility(View.GONE); - mBtEventApply.setText("报名链接"); - } else { - notifyEventStatus(); - } - } - - // 显示活动 以及报名的状态 - private void notifyEventStatus() { - int eventStatus = mDetail.getEvent().getStatus(); - int applyStatus = mDetail.getEvent().getApplyStatus(); - - if (applyStatus == Event.APPLYSTATUS_ATTEND) { - mBtAttend.setVisibility(View.VISIBLE); - } else { - mBtAttend.setVisibility(View.GONE); - } - - if (eventStatus == Event.EVNET_STATUS_APPLYING) { - mBtEventApply.setVisibility(View.VISIBLE); - mBtEventApply.setEnabled(false); - switch (applyStatus) { - case Event.APPLYSTATUS_CHECKING: - mBtEventApply.setText("待确认"); - break; - case Event.APPLYSTATUS_CHECKED: - mBtEventApply.setText("已确认"); - mBtEventApply.setVisibility(View.GONE); - mEventTip.setVisibility(View.VISIBLE); - break; - case Event.APPLYSTATUS_ATTEND: - mBtEventApply.setText("已出席"); - break; - case Event.APPLYSTATUS_CANCLE: - mBtEventApply.setText("已取消"); - mBtEventApply.setEnabled(true); - break; - case Event.APPLYSTATUS_REJECT: - mBtEventApply.setText("已拒绝"); - break; - default: - mBtEventApply.setText("我要报名"); - mBtEventApply.setEnabled(true); - break; - } - } else { - mBtEventApply.setVisibility(View.GONE); - } - } - - @Override - protected void showCommentView() { - if (mDetail != null) { - UIHelper.showComment(getActivity(), mId, CommentList.CATALOG_POST); - } - } - - @Override - protected int getCommentType() { - return CommentList.CATALOG_POST; - } - - @Override - protected int getFavoriteTargetType() { - return FavoriteList.TYPE_POST; - } - - @Override - protected int getFavoriteState() { - return mDetail.getFavorite(); - } - - @Override - protected void updateFavoriteChanged(int newFavoritedState) { - mDetail.setFavorite(newFavoritedState); - saveCache(mDetail); - } - - @Override - protected int getCommentCount() { - return mDetail.getAnswerCount(); - } - - @Override - public void onClick(View v) { - int id = v.getId(); - switch (id) { - case R.id.rl_event_location: - UIHelper.showEventLocation(getActivity(), mDetail.getEvent() - .getCity(), mDetail.getEvent().getSpot()); - break; - case R.id.bt_event_attend: - showEventApplies(); - break; - case R.id.bt_event_apply: - showEventApply(); - break; - default: - break; - } - } - - private void showEventApplies() { - Bundle args = new Bundle(); - args.putInt(BaseListFragment.BUNDLE_KEY_CATALOG, mDetail.getEvent() - .getId()); - UIHelper.showSimpleBack(getActivity(), SimpleBackPage.EVENT_APPLY, args); - } - - private final AsyncHttpResponseHandler mApplyHandler = new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - Result rs = XmlUtils.toBean(ResultBean.class, - new ByteArrayInputStream(arg2)).getResult(); - if (rs.OK()) { - AppContext.showToast("报名成功"); - mEventApplyDialog.dismiss(); - mDetail.getEvent().setApplyStatus(Event.APPLYSTATUS_CHECKING); - } else { - AppContext.showToast(rs.getErrorMessage()); - } - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) { - AppContext.showToast("报名失败"); - } - - @Override - public void onFinish() { - hideWaitDialog(); - } - }; - - /** - * 显示活动报名对话框 - */ - private void showEventApply() { - - if (mDetail.getEvent().getCategory() == 4) { - UIHelper.openSysBrowser(getActivity(), mDetail.getEvent().getUrl()); - return; - } - - if (!AppContext.getInstance().isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - if (mEventApplyDialog == null) { - mEventApplyDialog = new EventApplyDialog(getActivity(), mDetail.getEvent()); - mEventApplyDialog.setCanceledOnTouchOutside(true); - mEventApplyDialog.setCancelable(true); - mEventApplyDialog.setTitle("活动报名"); - mEventApplyDialog.setCanceledOnTouchOutside(true); - mEventApplyDialog.setNegativeButton(R.string.cancle, null); - mEventApplyDialog.setPositiveButton(R.string.ok, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface d, int which) { - EventApplyData data = null; - if ((data = mEventApplyDialog.getApplyData()) != null) { - data.setEvent(mId); - data.setUser(AppContext.getInstance() - .getLoginUid()); - showWaitDialog(R.string.progress_submit); - OSChinaApi.eventApply(data, mApplyHandler); - } - } - }); - } - - mEventApplyDialog.show(); - } - - @Override - protected String getShareTitle() { - return mDetail.getTitle(); - } - - @Override - protected String getShareContent() { - return StringUtils.getSubString(0, 55, - getFilterHtmlBody(mDetail.getBody())); - } - - @Override - protected String getShareUrl() { - return String.format(URLsUtils.URL_MOBILE + "question/%s_%s", mDetail.getAuthorId(), mId); - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/EventFragment.java b/app/src/main/java/net/oschina/app/fragment/EventFragment.java index 8e8c6d5911e892336ac46c916376f7e1bdd9b762..683cf3b114820642f3a8d170f4f39300fa4c6464 100644 --- a/app/src/main/java/net/oschina/app/fragment/EventFragment.java +++ b/app/src/main/java/net/oschina/app/fragment/EventFragment.java @@ -1,9 +1,13 @@ package net.oschina.app.fragment; -import java.io.InputStream; -import java.io.Serializable; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; -import net.oschina.app.AppContext; import net.oschina.app.R; import net.oschina.app.adapter.EventAdapter; import net.oschina.app.api.remote.OSChinaApi; @@ -11,29 +15,24 @@ import net.oschina.app.base.BaseListFragment; import net.oschina.app.bean.Constants; import net.oschina.app.bean.Event; import net.oschina.app.bean.EventList; +import net.oschina.app.improve.account.AccountHelper; import net.oschina.app.ui.empty.EmptyLayout; import net.oschina.app.util.UIHelper; import net.oschina.app.util.XmlUtils; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; + +import java.io.InputStream; +import java.io.Serializable; /** * 活动列表fragment - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @version 创建时间:2014年12月8日 下午5:17:32 - * */ public class EventFragment extends BaseListFragment { public static final String BUNDLE_KEY_EVENT_TYPE = "eventlist_type"; - protected static final String TAG = EventFragment.class.getSimpleName(); private static final String CACHE_KEY_PREFIX = "eventlist_"; private int event_type; @@ -94,7 +93,7 @@ public class EventFragment extends BaseListFragment { mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); requestData(true); } else { - if (AppContext.getInstance().isLogin()) { + if (AccountHelper.isLogin()) { mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); requestData(true); } else { @@ -111,8 +110,8 @@ public class EventFragment extends BaseListFragment { super.requestData(refresh); return; } - if (AppContext.getInstance().isLogin()) { - mCatalog = AppContext.getInstance().getLoginUid(); + if (AccountHelper.isLogin()) { + mCatalog = (int) AccountHelper.getUserId(); super.requestData(refresh); } else { mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); @@ -127,8 +126,7 @@ public class EventFragment extends BaseListFragment { @Override protected EventList parseList(InputStream is) throws Exception { - EventList list = XmlUtils.toBean(EventList.class, is); - return list; + return XmlUtils.toBean(EventList.class, is); } @Override @@ -143,7 +141,7 @@ public class EventFragment extends BaseListFragment { @Override public void onItemClick(AdapterView parent, View view, int position, - long id) { + long id) { Event event = mAdapter.getItem(position); if (event != null) UIHelper.showEventDetail(view.getContext(), event.getId()); diff --git a/app/src/main/java/net/oschina/app/fragment/ExploreFragment.java b/app/src/main/java/net/oschina/app/fragment/ExploreFragment.java deleted file mode 100644 index e8bc09ddd88ba7c5e3d1b1c0a0442b3142febc1d..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/ExploreFragment.java +++ /dev/null @@ -1,111 +0,0 @@ -package net.oschina.app.fragment; - -import android.content.Intent; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import net.oschina.app.R; -import net.oschina.app.base.BaseFragment; -import net.oschina.app.bean.SimpleBackPage; -import net.oschina.app.ui.FindUserActivity; -import net.oschina.app.ui.ShakeActivity; -import net.oschina.app.util.UIHelper; - -import butterknife.ButterKnife; -import butterknife.InjectView; - -/** - * 发现页面 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2014年11月4日 下午3:34:07 - * - */ - -public class ExploreFragment extends BaseFragment { - - @InjectView(R.id.rl_active) - View mRlActive; - - @InjectView(R.id.rl_find_osc) - View mFindOSCer; - - @InjectView(R.id.rl_city) - View mCity; - - @InjectView(R.id.rl_activities) - View mActivities; - - @InjectView(R.id.rl_scan) - View mScan; - - @InjectView(R.id.rl_shake) - View mShake; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); - View view = inflater.inflate(R.layout.fragment_explore, null); - ButterKnife.inject(this, view); - initView(view); - return view; - } - - @Override - public void onClick(View v) { - int id = v.getId(); - switch (id) { - case R.id.rl_active: - UIHelper.showMyActive(getActivity()); - break; - case R.id.rl_find_osc: - showFindUser(); - break; - case R.id.rl_city: - UIHelper.showSimpleBack(getActivity(), SimpleBackPage.SAME_CITY); - break; - case R.id.rl_activities: - UIHelper.showSimpleBack(getActivity(), SimpleBackPage.EVENT_LIST); - break; - case R.id.rl_scan: - UIHelper.showScanActivity(getActivity()); - break; - case R.id.rl_shake: - showShake(); - break; - default: - break; - } - } - - private void showShake() { - Intent intent = new Intent(); - intent.setClass(getActivity(), ShakeActivity.class); - getActivity().startActivity(intent); - } - - private void showFindUser() { - Intent intent = new Intent(); - intent.setClass(getActivity(), FindUserActivity.class); - getActivity().startActivity(intent); - } - - @Override - public void initView(View view) { - mRlActive.setOnClickListener(this); - - mFindOSCer.setOnClickListener(this); - mCity.setOnClickListener(this); - mActivities.setOnClickListener(this); - mScan.setOnClickListener(this); - mShake.setOnClickListener(this); - } - - @Override - public void initData() { - - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/FeedBackFragment.java b/app/src/main/java/net/oschina/app/fragment/FeedBackFragment.java deleted file mode 100644 index a6f5e403f117c1d24837b918efed9852ffe0bd68..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/FeedBackFragment.java +++ /dev/null @@ -1,96 +0,0 @@ -package net.oschina.app.fragment; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseFragment; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.TDevice; - -import cz.msebera.android.httpclient.Header; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import butterknife.ButterKnife; -import butterknife.InjectView; - -import com.loopj.android.http.AsyncHttpResponseHandler; - -public class FeedBackFragment extends BaseFragment { - @InjectView(R.id.et_feedback) - EditText mEtContent; - @InjectView(R.id.et_contact) - EditText mEtContact; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); - View view = inflater.inflate(R.layout.fragment_feedback, null); - ButterKnife.inject(this, view); - initView(view); - return view; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.submit_menu, menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.public_menu_send: - String data = mEtContent.getText().toString(); - if (StringUtils.isEmpty(data)) { - AppContext.showToast("你忘记写建议咯"); - } else { - data += "
    "; - data += mEtContact.getText() + "
    "; - data += TDevice.getVersionName() + "(" - + TDevice.getVersionCode() + ")
    "; - OSChinaApi.feedback(data, new AsyncHttpResponseHandler() { - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - AppContext.showToast("已收到你的建议,谢谢"); - getActivity().finish(); - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) { - AppContext.showToast("网络异常,请稍后重试"); - } - }); - } - break; - } - return true; - } - - @Override - public void onClick(View v) { - - } - - @Override - public void initView(View view) { - - } - - @Override - public void initData() { - - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/FriendsFragment.java b/app/src/main/java/net/oschina/app/fragment/FriendsFragment.java deleted file mode 100644 index 9a84cd48dfdb7b89d152890c7727fb4e72b84469..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/FriendsFragment.java +++ /dev/null @@ -1,137 +0,0 @@ -package net.oschina.app.fragment; - -import java.io.InputStream; -import java.io.Serializable; -import java.util.List; - -import net.oschina.app.AppContext; -import net.oschina.app.adapter.FriendAdapter; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.bean.Entity; -import net.oschina.app.bean.Friend; -import net.oschina.app.bean.FriendsList; -import net.oschina.app.bean.Notice; -import net.oschina.app.service.NoticeUtils; -import net.oschina.app.ui.MainActivity; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; -import net.oschina.app.viewpagerfragment.NoticeViewPagerFragment; -import android.annotation.TargetApi; -import android.os.Build; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; - -/** - * 关注、粉丝 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年11月6日 上午11:15:37 - * - */ -@TargetApi(Build.VERSION_CODES.HONEYCOMB) -public class FriendsFragment extends BaseListFragment { - - public final static String BUNDLE_KEY_UID = "UID"; - - protected static final String TAG = FriendsFragment.class.getSimpleName(); - private static final String CACHE_KEY_PREFIX = "friend_list"; - - private int mUid; - - @Override - public void initView(View view) { - super.initView(view); - } - - @Override - public void onCreate(android.os.Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Bundle args = getArguments(); - if (args != null) { - mUid = args.getInt(BUNDLE_KEY_UID, 0); - } - } - - @Override - public void onResume() { - if (mCatalog == FriendsList.TYPE_FANS - && mUid == AppContext.getInstance().getLoginUid()) { - refreshNotice(); - } - super.onResume(); - } - - private void refreshNotice() { - Notice notice = MainActivity.mNotice; - if (notice != null && notice.getNewFansCount() > 0) { - onRefresh(); - } - } - - @Override - protected FriendAdapter getListAdapter() { - return new FriendAdapter(); - } - - @Override - protected String getCacheKeyPrefix() { - return CACHE_KEY_PREFIX + "_" + mCatalog + "_" + mUid; - } - - @Override - protected FriendsList parseList(InputStream is) throws Exception { - FriendsList list = XmlUtils.toBean(FriendsList.class, is); - return list; - } - - @Override - protected FriendsList readList(Serializable seri) { - return ((FriendsList) seri); - } - - @Override - protected boolean compareTo(List data, Entity enity) { - int s = data.size(); - if (enity != null) { - for (int i = 0; i < s; i++) { - if (((Friend) enity).getUserid() == ((Friend) data.get(i)) - .getUserid()) { - return true; - } - } - } - return false; - } - - @Override - protected void sendRequestData() { - OSChinaApi.getFriendList(mUid, mCatalog, mCurrentPage, mHandler); - } - - @Override - protected void onRefreshNetworkSuccess() { - if ((NoticeViewPagerFragment.sCurrentPage == 3 || NoticeViewPagerFragment.sShowCount[3] > 0) - && mCatalog == FriendsList.TYPE_FANS - && mUid == AppContext.getInstance().getLoginUid()) { - NoticeUtils.clearNotice(Notice.TYPE_NEWFAN); - UIHelper.sendBroadcastForNotice(getActivity()); - } - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Friend item = (Friend) mAdapter.getItem(position); - if (item != null) { - if (mUid == AppContext.getInstance().getLoginUid()) { - UIHelper.showMessageDetail(getActivity(), item.getUserid(), - item.getName()); - return; - } - UIHelper.showUserCenter(getActivity(), item.getUserid(), - item.getName()); - } - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/MessageDetailFragment.java b/app/src/main/java/net/oschina/app/fragment/MessageDetailFragment.java deleted file mode 100644 index 15c195191915811ad546ef70b4ee793347c24fec..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/MessageDetailFragment.java +++ /dev/null @@ -1,461 +0,0 @@ -package net.oschina.app.fragment; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.Color; -import android.os.Bundle; -import android.text.Editable; -import android.util.SparseArray; -import android.view.View; -import android.view.WindowManager; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemLongClickListener; - -import com.loopj.android.http.AsyncHttpResponseHandler; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.adapter.MessageDetailAdapter; -import net.oschina.app.api.OperationResponseHandler; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseActivity; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.Comment; -import net.oschina.app.bean.CommentList; -import net.oschina.app.bean.Constants; -import net.oschina.app.bean.MessageDetail; -import net.oschina.app.bean.MessageDetailList; -import net.oschina.app.bean.Result; -import net.oschina.app.bean.ResultBean; -import net.oschina.app.bean.User; -import net.oschina.app.emoji.KJEmojiFragment; -import net.oschina.app.emoji.OnSendClickListener; -import net.oschina.app.ui.empty.EmptyLayout; -import net.oschina.app.util.DialogHelp; -import net.oschina.app.util.HTMLUtil; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; - -import cz.msebera.android.httpclient.Header; -import org.kymjs.kjframe.utils.StringUtils; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -/** - * 与某人的聊天记录界面(私信详情) - * - * @author kymjs (http://www.kymjs.com/) - * - */ -public class MessageDetailFragment extends BaseListFragment implements - OnItemLongClickListener, OnSendClickListener,MessageDetailAdapter.OnRetrySendMessageListener{ - protected static final String TAG = ActiveFragment.class.getSimpleName(); - public static final String BUNDLE_KEY_FID = "BUNDLE_KEY_FID"; - public static final String BUNDLE_KEY_FNAME = "BUNDLE_KEY_FNAME"; - private static final String CACHE_KEY_PREFIX = "message_detail_list"; - //时间间隔(要求:聊天时间显示,时间间隔为五分钟以上才显示出来) - private final static long TIME_INTERVAL = 1000 * 60 * 5; - - private int mFid; - private String mFName; - private int mMsgTag; - private int mPageCount; - private long mLastShowDate; //最后显示出来的时间 - public KJEmojiFragment emojiFragment = new KJEmojiFragment(); - //存放正在发送的消息,key 为生成的一个临时messageID(msgTag),value为Message实体 - //当消息发送成功后,从mSendingMsgs删除对应的Message实体 - private SparseArray mSendingMsgs = new SparseArray(); - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (mErrorLayout != null) { - mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); - mErrorLayout.setErrorMessage(getString(R.string.unlogin_tip)); - } - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Bundle args = getArguments(); - if (args != null) { - mFid = args.getInt(BUNDLE_KEY_FID); - mFName = args.getString(BUNDLE_KEY_FNAME); - mCatalog = CommentList.CATALOG_MESSAGE; - } - IntentFilter filter = new IntentFilter(Constants.INTENT_ACTION_LOGOUT); - getActivity().registerReceiver(mReceiver, filter); - - ((BaseActivity) getActivity()).setActionBarTitle(mFName); - - int mode = WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN - | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - getActivity().getWindow().setSoftInputMode(mode); - - getActivity().getSupportFragmentManager().beginTransaction() - .replace(R.id.emoji_container, emojiFragment).commit(); - } - - @Override - public void onDestroy() { - getActivity().unregisterReceiver(mReceiver); - super.onDestroy(); - } - - @Override - public boolean onBackPressed() { - if (emojiFragment.isShowEmojiKeyBoard()) { - emojiFragment.hideAllKeyBoard(); - return true; - } else { - return super.onBackPressed(); - } - } - - @Override - protected MessageDetailAdapter getListAdapter() { - MessageDetailAdapter adapter = new MessageDetailAdapter(); - adapter.setOnRetrySendMessageListener(this); - return adapter; - } - - @Override - protected String getCacheKeyPrefix() { - return CACHE_KEY_PREFIX + mFid; - } - - @Override - protected MessageDetailList parseList(InputStream is) throws Exception { - MessageDetailList list = XmlUtils.toBean(MessageDetailList.class, is); - handleShowDate(list.getList()); - mPageCount = (int) Math.ceil((float) list.getMessageCount() / getPageSize()); - return list; - } - - @Override - protected MessageDetailList readList(Serializable seri) { - MessageDetailList list = ((MessageDetailList) seri); - handleShowDate(list.getList()); - return list; - } - - /** - * 处理时间显示,设置哪些需要显示时间,哪些不需要显示时间 - * @param list - */ - private void handleShowDate(List list) { - MessageDetail msg = null; - long lastGroupTime = 0l; - //因为获得的列表是按时间降序的,所以需要倒着遍历 - for (int i = list.size() - 1; i >= 0; i--) { - msg = list.get(i); - Date date = net.oschina.app.util.StringUtils.toDate(msg.getPubDate()); - if (date != null && isNeedShowDate(date.getTime(), lastGroupTime)) { - lastGroupTime = date.getTime(); - msg.setShowDate(true); - } - } - //只设置最新的时间 - if (lastGroupTime > mLastShowDate) { - mLastShowDate = lastGroupTime; - } - } - - private boolean isNeedShowDate(long currentTime,long lastTime){ - return currentTime - lastTime > TIME_INTERVAL; - } - - @Override - public void initView(View view) { - super.initView(view); - mListView.setDivider(null); - mListView.setDividerHeight(0); - if(!AppContext.getNightModeSwitch()) { - mListView.setBackgroundColor(Color.parseColor("#ebebeb")); - } - mListView.setOnItemLongClickListener(this); - //移除父类设置的OnScrollListener,这里不需要下拉加载 - mListView.setOnScrollListener(null); - mErrorLayout.setOnLayoutClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (AppContext.getInstance().isLogin()) { - requestData(false); - } else { - UIHelper.showLoginActivity(getActivity()); - } - } - }); - } - - @Override - protected void requestData(boolean refresh) { - mErrorLayout.setErrorMessage(""); - if (AppContext.getInstance().isLogin()) { - super.requestData(refresh); - } else { - mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); - mErrorLayout.setErrorMessage(getString(R.string.unlogin_tip)); - } - } - - @Override - protected void sendRequestData() { - OSChinaApi.getChatMessageList(mFid, mCurrentPage, mHandler); - } - - @Override - protected boolean isReadCacheData(boolean refresh) { - if (!TDevice.hasInternet()) { - return true; - } else { - return false; - } - } - - @Override - protected void executeOnLoadDataSuccess(List data) { - mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); - if (data == null) { - data = new ArrayList(1); - } - if (mAdapter != null) { - if (mCurrentPage == 0) - mAdapter.clear(); - mAdapter.addData(data); - if (data.size() == 0 && mState == STATE_REFRESH) { - mErrorLayout.setErrorType(EmptyLayout.NODATA); - } else if (data.size() < getPageSize()) { - mAdapter.setState(ListBaseAdapter.STATE_OTHER); - } else { - mAdapter.setState(ListBaseAdapter.STATE_LOAD_MORE); - } - mAdapter.notifyDataSetChanged(); - //只有第一次加载,才需要滚动到底部 - if (mCurrentPage == 0) - mListView.setSelection(mListView.getBottom()); - else if (data.size() > 1) { - mListView.setSelection(data.size() - 1); - } - } - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) {} - - @Override - public boolean onItemLongClick(AdapterView parent, View view, - int position, long id) { - final MessageDetail message = mAdapter.getItem(position); - DialogHelp.getSelectDialog(getActivity(), getResources().getStringArray(R.array.message_list_options), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - switch (i) { - case 0: - TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(message - .getContent())); - break; - case 1: - handleDeleteMessage(message); - break; - default: - break; - } - } - }).show(); - return true; - } - - // 下拉加载数据 - @Override - public void onRefresh() { - if (mState == STATE_REFRESH) { - return; - } - if(mCurrentPage==mPageCount-1){ - AppContext.showToastShort("已加载全部数据!"); - setSwipeRefreshLoadedState(); - return; - } - // 设置顶部正在刷新 - mListView.setSelection(0); - setSwipeRefreshLoadingState(); - mState = STATE_REFRESH; - mCurrentPage++; - requestData(true); - } - - public void showFriendUserCenter(){ - UIHelper.showUserCenter(getActivity(), mFid, mFName); - } - - @Override - public void onResume() { - super.onResume(); - emojiFragment.hideFlagButton(); - } - - private void handleDeleteMessage(final MessageDetail message) { - DialogHelp.getConfirmDialog(getActivity(), "是否删除该私信?", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - showWaitDialog(R.string.progress_submit); - OSChinaApi.deleteComment(mFid, - CommentList.CATALOG_MESSAGE, message.getId(), - message.getAuthorId(), - new DeleteMessageOperationHandler(message)); - } - }).show(); - } - - class DeleteMessageOperationHandler extends OperationResponseHandler { - - public DeleteMessageOperationHandler(Object... args) { - super(args); - } - - @Override - public void onSuccess(int code, ByteArrayInputStream is, Object[] args) - throws Exception { - Result res = XmlUtils.toBean(ResultBean.class, is).getResult(); - if (res.OK()) { - Comment msg = (Comment) args[0]; - mAdapter.removeItem(msg); - mAdapter.notifyDataSetChanged(); - hideWaitDialog(); - AppContext.showToastShort(R.string.tip_delete_success); - } else { - AppContext.showToastShort(res.getErrorMessage()); - hideWaitDialog(); - } - } - - @Override - public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { - - } - - @Override - public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { - AppContext.showToastShort(R.string.tip_delete_faile); - hideWaitDialog(); - } - } - - @Override - public void onClickSendButton(Editable str) { - if (!AppContext.getInstance().isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - if (StringUtils.isEmpty(str)) { - AppContext.showToastShort(R.string.tip_content_empty); - return; - } - MessageDetail message = new MessageDetail(); - User user = AppContext.getInstance().getLoginUser(); - int msgTag = mMsgTag++; - message.setId(msgTag); - message.setPortrait(user.getPortrait()); - message.setAuthor(user.getName()); - message.setAuthorId(user.getId()); - message.setContent(str.toString()); - sendMessage(message); - } - - /** - * 发送消息 - * @param msg - */ - private void sendMessage(MessageDetail msg){ - msg.setStatus(MessageDetail.MessageStatus.SENDING); - Date date = new Date(); - msg.setPubDate(net.oschina.app.util.StringUtils.getDateString(date)); - //如果此次发表的时间距离上次的时间达到了 TIME_INTERVAL 的间隔要求,则显示时间 - if(isNeedShowDate(date.getTime(),mLastShowDate)) { - msg.setShowDate(true); - mLastShowDate = date.getTime(); - } - - //如果待发送列表没有此条消息,说明是新消息,不是发送失败再次发送的,不需要再次添加 - if(mSendingMsgs.indexOfKey(msg.getId())<0) { - mSendingMsgs.put(msg.getId(), msg); - mAdapter.addItem(0, msg); - mListView.setSelection(mListView.getBottom()); - }else{ - mAdapter.notifyDataSetChanged(); - } - OSChinaApi.publicMessage(msg.getAuthorId(), mFid, msg.getContent(), new SendMessageResponseHandler(msg.getId())); - } - - @Override - public void onClickFlagButton() {} - - @Override - public void onRetrySendMessage(int msgId) { - MessageDetail message = mSendingMsgs.get(msgId); - if (message != null) { - sendMessage(message); - } - } - - class SendMessageResponseHandler extends AsyncHttpResponseHandler{ - - private int msgTag; - - public SendMessageResponseHandler(int msgTag) { - this.msgTag = msgTag; - } - - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - try { - ResultBean resb = XmlUtils.toBean(ResultBean.class, - new ByteArrayInputStream(arg2)); - Result res = resb.getResult(); - if (res.OK()) { - //从mSendingMsgs获取发送时放入的MessageDetail实体 - MessageDetail message = mSendingMsgs.get(this.msgTag); - MessageDetail serverMsg = resb.getMessage(); - //把id设置为服务器返回的id - message.setId(serverMsg.getId()); - message.setStatus(MessageDetail.MessageStatus.NORMAL); - //从待发送列表移除 - mSendingMsgs.remove(this.msgTag); - mAdapter.notifyDataSetChanged(); - } else { - error(); - AppContext.showToastShort(res.getErrorMessage()); - } - emojiFragment.clean(); - } catch (Exception e) { - e.printStackTrace(); - onFailure(arg0, arg1, arg2, e); - } - } - - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, Throwable arg3) { - error(); - } - - private void error(){ - mSendingMsgs.get(this.msgTag).setStatus(MessageDetail.MessageStatus.ERROR); - mAdapter.notifyDataSetChanged(); - } - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/MessageFragment.java b/app/src/main/java/net/oschina/app/fragment/MessageFragment.java deleted file mode 100644 index 39d45505ccff4cd86b053dcfae267314107b4c52..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/MessageFragment.java +++ /dev/null @@ -1,239 +0,0 @@ -package net.oschina.app.fragment; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemLongClickListener; -import cz.msebera.android.httpclient.Header; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.adapter.MessageAdapter; -import net.oschina.app.api.OperationResponseHandler; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.bean.Constants; -import net.oschina.app.bean.MessageList; -import net.oschina.app.bean.Messages; -import net.oschina.app.bean.Notice; -import net.oschina.app.bean.Result; -import net.oschina.app.bean.ResultBean; -import net.oschina.app.service.NoticeUtils; -import net.oschina.app.ui.MainActivity; -import net.oschina.app.ui.empty.EmptyLayout; -import net.oschina.app.util.DialogHelp; -import net.oschina.app.util.HTMLUtil; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; -import net.oschina.app.viewpagerfragment.NoticeViewPagerFragment; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.Serializable; - -public class MessageFragment extends BaseListFragment implements - OnItemLongClickListener { - protected static final String TAG = ActiveFragment.class.getSimpleName(); - private static final String CACHE_KEY_PREFIX = "message_list"; - private boolean mIsWatingLogin; - - private final BroadcastReceiver mLogoutReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - if (mErrorLayout != null) { - mIsWatingLogin = true; - mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); - mErrorLayout.setErrorMessage(getString(R.string.unlogin_tip)); - } - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - IntentFilter filter = new IntentFilter(Constants.INTENT_ACTION_LOGOUT); - getActivity().registerReceiver(mLogoutReceiver, filter); - } - - @Override - public void onDestroy() { - getActivity().unregisterReceiver(mLogoutReceiver); - super.onDestroy(); - } - - @Override - public void onResume() { - if (mIsWatingLogin) { - mCurrentPage = 0; - mState = STATE_REFRESH; - requestData(false); - } - refreshNotice(); - super.onResume(); - } - - private void refreshNotice() { - Notice notice = MainActivity.mNotice; - if (notice != null && notice.getMsgCount() > 0) { - onRefresh(); - } - } - - @Override - protected MessageAdapter getListAdapter() { - return new MessageAdapter(); - } - - @Override - protected String getCacheKeyPrefix() { - return CACHE_KEY_PREFIX; - } - - @Override - protected MessageList parseList(InputStream is) throws Exception { - MessageList list = XmlUtils.toBean(MessageList.class, is); - return list; - } - - @Override - protected MessageList readList(Serializable seri) { - return ((MessageList) seri); - } - - @Override - public void initView(View view) { - super.initView(view); - mListView.setDivider(null); - mListView.setDividerHeight(0); - mListView.setOnItemLongClickListener(this); - mErrorLayout.setOnLayoutClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (AppContext.getInstance().isLogin()) { - mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); - requestData(false); - } else { - UIHelper.showLoginActivity(getActivity()); - } - } - }); - if (AppContext.getInstance().isLogin()) { - UIHelper.sendBroadcastForNotice(getActivity()); - } - } - - @Override - protected void requestData(boolean refresh) { - if (AppContext.getInstance().isLogin()) { - mIsWatingLogin = false; - super.requestData(refresh); - } else { - mIsWatingLogin = true; - mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); - mErrorLayout.setErrorMessage(getString(R.string.unlogin_tip)); - } - } - - @Override - protected void sendRequestData() { - OSChinaApi.getMessageList(AppContext.getInstance().getLoginUid(), - mCurrentPage, mHandler); - } - - @Override - protected void onRefreshNetworkSuccess() { - if (2 == NoticeViewPagerFragment.sCurrentPage - || NoticeViewPagerFragment.sShowCount[2] > 0) { // 在page中第三个位置 - NoticeUtils.clearNotice(Notice.TYPE_MESSAGE); - UIHelper.sendBroadcastForNotice(getActivity()); - } - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Messages message = (Messages) mAdapter.getItem(position); - if (message != null) - UIHelper.showMessageDetail(getActivity(), message.getFriendId(), - message.getFriendName()); - } - - @Override - public boolean onItemLongClick(AdapterView parent, View view, - int position, long id) { - final Messages message = (Messages) mAdapter.getItem(position); - DialogHelp.getSelectDialog(getActivity(), getResources().getStringArray(R.array.message_list_options), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - switch (i) { - case 0: - TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(message - .getContent())); - break; - case 1: - handleDeleteMessage(message); - break; - default: - break; - } - } - }).show(); - return true; - } - - private void handleDeleteMessage(final Messages message) { - - DialogHelp.getConfirmDialog(getActivity(), getString(R.string.confirm_delete_message, - message.getFriendName()), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - showWaitDialog(R.string.progress_submit); - - OSChinaApi.deleteMessage(AppContext.getInstance() - .getLoginUid(), message.getFriendId(), - new DeleteMessageOperationHandler(message)); - } - }).show(); - } - - class DeleteMessageOperationHandler extends OperationResponseHandler { - - public DeleteMessageOperationHandler(Object... args) { - super(args); - } - - @Override - public void onSuccess(int code, ByteArrayInputStream is, Object[] args) - throws Exception { - Result res = XmlUtils.toBean(ResultBean.class, is).getResult(); - if (res.OK()) { - Messages msg = (Messages) args[0]; - mAdapter.removeItem(msg); - mAdapter.notifyDataSetChanged(); - hideWaitDialog(); - AppContext.showToastShort(R.string.tip_delete_success); - } else { - AppContext.showToastShort(res.getErrorMessage()); - hideWaitDialog(); - } - } - - @Override - public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { - - } - - @Override - public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { - AppContext.showToastShort(R.string.tip_delete_faile); - hideWaitDialog(); - } - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/MyInformationFragment.java b/app/src/main/java/net/oschina/app/fragment/MyInformationFragment.java deleted file mode 100644 index b2f295f95cdcf268a955c86de54157d74daf93f9..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/MyInformationFragment.java +++ /dev/null @@ -1,421 +0,0 @@ -package net.oschina.app.fragment; - -import java.io.ByteArrayInputStream; -import java.io.Serializable; -import java.lang.ref.WeakReference; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseFragment; -import net.oschina.app.bean.Constants; -import net.oschina.app.bean.MyInformation; -import net.oschina.app.bean.Notice; -import net.oschina.app.bean.SimpleBackPage; -import net.oschina.app.bean.User; -import net.oschina.app.cache.CacheManager; -import net.oschina.app.ui.MainActivity; -import net.oschina.app.ui.MyQrodeDialog; -import net.oschina.app.ui.empty.EmptyLayout; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; -import net.oschina.app.widget.AvatarView; -import net.oschina.app.widget.BadgeView; - - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import butterknife.ButterKnife; -import butterknife.InjectView; -import cz.msebera.android.httpclient.Header; - -import com.loopj.android.http.AsyncHttpResponseHandler; - -/** - * 登录用户中心页面 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @author kymjs (http://my.oschina.net/kymjs) - * @version 创建时间:2014年10月30日 下午4:05:47 - */ -public class MyInformationFragment extends BaseFragment { - - public static final int sChildView = 9; // 在没有加入TeamList控件时rootview有多少子布局 - - @InjectView(R.id.iv_avatar) - AvatarView mIvAvatar; - @InjectView(R.id.iv_gender) - ImageView mIvGender; - @InjectView(R.id.tv_name) - TextView mTvName; - @InjectView(R.id.tv_score) - TextView mTvScore; - @InjectView(R.id.tv_favorite) - TextView mTvFavorite; - @InjectView(R.id.tv_following) - TextView mTvFollowing; - @InjectView(R.id.tv_follower) - TextView mTvFans; - @InjectView(R.id.tv_mes) - View mMesView; - @InjectView(R.id.error_layout) - EmptyLayout mErrorLayout; - @InjectView(R.id.iv_qr_code) - ImageView mQrCode; - @InjectView(R.id.ll_user_container) - View mUserContainer; - @InjectView(R.id.rl_user_unlogin) - View mUserUnLogin; - @InjectView(R.id.rootview) - LinearLayout rootView; - - private static BadgeView mMesCount; - - private boolean mIsWatingLogin; - - private User mInfo; - private AsyncTask mCacheTask; - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(Constants.INTENT_ACTION_LOGOUT)) { - if (mErrorLayout != null) { - mIsWatingLogin = true; - steupUser(); - mMesCount.hide(); - } - } else if (action.equals(Constants.INTENT_ACTION_USER_CHANGE)) { - requestData(true); - } else if (action.equals(Constants.INTENT_ACTION_NOTICE)) { - setNotice(); - } - } - }; - - private final AsyncHttpResponseHandler mHandler = new AsyncHttpResponseHandler() { - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - try { - mInfo = XmlUtils.toBean(MyInformation.class, - new ByteArrayInputStream(arg2)).getUser(); - if (mInfo != null) { - fillUI(); - AppContext.getInstance().updateUserInfo(mInfo); - new SaveCacheTask(getActivity(), mInfo, getCacheKey()) - .execute(); - } else { - onFailure(arg0, arg1, arg2, new Throwable()); - } - } catch (Exception e) { - e.printStackTrace(); - onFailure(arg0, arg1, arg2, e); - } - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) {} - }; - - private void steupUser() { - if (mIsWatingLogin) { - mUserContainer.setVisibility(View.GONE); - mUserUnLogin.setVisibility(View.VISIBLE); - } else { - mUserContainer.setVisibility(View.VISIBLE); - mUserUnLogin.setVisibility(View.GONE); - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - IntentFilter filter = new IntentFilter(Constants.INTENT_ACTION_LOGOUT); - filter.addAction(Constants.INTENT_ACTION_USER_CHANGE); - getActivity().registerReceiver(mReceiver, filter); - } - - @Override - public void onResume() { - super.onResume(); - setNotice(); - } - - public void setNotice() { - if (MainActivity.mNotice != null) { - - Notice notice = MainActivity.mNotice; - int atmeCount = notice.getAtmeCount();// @我 - int msgCount = notice.getMsgCount();// 留言 - int reviewCount = notice.getReviewCount();// 评论 - int newFansCount = notice.getNewFansCount();// 新粉丝 - int newLikeCount = notice.getNewLikeCount();// 获得点赞 - int activeCount = atmeCount + reviewCount + msgCount + newFansCount + newLikeCount;// 信息总数 - if (activeCount > 0) { - mMesCount.setText(activeCount + ""); - mMesCount.show(); - } else { - mMesCount.hide(); - } - - } else { - mMesCount.hide(); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - getActivity().unregisterReceiver(mReceiver); - } - - @Override - public View onCreateView(LayoutInflater inflater, - @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_my_information, - container, false); - ButterKnife.inject(this, view); - initView(view); - return view; - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - requestData(true); - mInfo = AppContext.getInstance().getLoginUser(); - fillUI(); - } - - @Override - public void initView(View view) { - mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); - mIvAvatar.setOnClickListener(this); - mErrorLayout.setOnLayoutClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (AppContext.getInstance().isLogin()) { - requestData(true); - } else { - UIHelper.showLoginActivity(getActivity()); - } - } - }); - view.findViewById(R.id.ly_favorite).setOnClickListener(this); - view.findViewById(R.id.ly_following).setOnClickListener(this); - view.findViewById(R.id.ly_follower).setOnClickListener(this); - view.findViewById(R.id.rl_message).setOnClickListener(this); - view.findViewById(R.id.rl_team).setOnClickListener(this); - view.findViewById(R.id.rl_blog).setOnClickListener(this); - view.findViewById(R.id.rl_note).setOnClickListener( - new OnClickListener() { - @Override - public void onClick(View v) { - UIHelper.showSimpleBack(getActivity(), - SimpleBackPage.NOTE); - } - }); - mUserUnLogin.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - UIHelper.showLoginActivity(getActivity()); - } - }); - - mMesCount = new BadgeView(getActivity(), mMesView); - mMesCount.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10); - mMesCount.setBadgePosition(BadgeView.POSITION_CENTER); - mMesCount.setGravity(Gravity.CENTER); - mMesCount.setBackgroundResource(R.drawable.notification_bg); - mQrCode.setOnClickListener(this); - // // 初始化团队列表数据 - // String cache = PreferenceHelper.readString(getActivity(), - // TEAM_LIST_FILE, TEAM_LIST_KEY); - // if (!StringUtils.isEmpty(cache)) { - // List teams = TeamList.toTeamList(cache); - // addTeamLayout(teams); - // } - } - - private void fillUI() { - if (mInfo == null) - return; - mIvAvatar.setAvatarUrl(mInfo.getPortrait()); - mTvName.setText(mInfo.getName()); - mIvGender - .setImageResource(StringUtils.toInt(mInfo.getGender()) != 2 ? R.drawable.userinfo_icon_male - : R.drawable.userinfo_icon_female); - mTvScore.setText(String.valueOf(mInfo.getScore())); - mTvFavorite.setText(String.valueOf(mInfo.getFavoritecount())); - mTvFollowing.setText(String.valueOf(mInfo.getFollowers())); - mTvFans.setText(String.valueOf(mInfo.getFans())); - } - - private void requestData(boolean refresh) { - if (AppContext.getInstance().isLogin()) { - mIsWatingLogin = false; - String key = getCacheKey(); - if (refresh || TDevice.hasInternet() - && (!CacheManager.isExistDataCache(getActivity(), key))) { - sendRequestData(); - } else { - readCacheData(key); - } - } else { - mIsWatingLogin = true; - } - steupUser(); - } - - private void readCacheData(String key) { - cancelReadCacheTask(); - mCacheTask = new CacheTask(getActivity()).execute(key); - } - - private void cancelReadCacheTask() { - if (mCacheTask != null) { - mCacheTask.cancel(true); - mCacheTask = null; - } - } - - private void sendRequestData() { - int uid = AppContext.getInstance().getLoginUid(); - OSChinaApi.getMyInformation(uid, mHandler); - } - - private String getCacheKey() { - return "my_information" + AppContext.getInstance().getLoginUid(); - } - - private class CacheTask extends AsyncTask { - private final WeakReference mContext; - - private CacheTask(Context context) { - mContext = new WeakReference(context); - } - - @Override - protected User doInBackground(String... params) { - Serializable seri = CacheManager.readObject(mContext.get(), - params[0]); - if (seri == null) { - return null; - } else { - return (User) seri; - } - } - - @Override - protected void onPostExecute(User info) { - super.onPostExecute(info); - if (info != null) { - mInfo = info; - // mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); - // } else { - // mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); - fillUI(); - } - } - } - - private class SaveCacheTask extends AsyncTask { - private final WeakReference mContext; - private final Serializable seri; - private final String key; - - private SaveCacheTask(Context context, Serializable seri, String key) { - mContext = new WeakReference(context); - this.seri = seri; - this.key = key; - } - - @Override - protected Void doInBackground(Void... params) { - CacheManager.saveObject(mContext.get(), seri, key); - return null; - } - } - - @Override - public void onClick(View v) { - if (mIsWatingLogin) { - AppContext.showToast(R.string.unlogin); - UIHelper.showLoginActivity(getActivity()); - return; - } - final int id = v.getId(); - switch (id) { - case R.id.iv_avatar: - UIHelper.showSimpleBack(getActivity(), - SimpleBackPage.MY_INFORMATION_DETAIL); - break; - case R.id.iv_qr_code: - showMyQrCode(); - break; - case R.id.ly_following: - UIHelper.showFriends(getActivity(), AppContext.getInstance() - .getLoginUid(), 0); - break; - case R.id.ly_follower: - UIHelper.showFriends(getActivity(), AppContext.getInstance() - .getLoginUid(), 1); - break; - case R.id.ly_favorite: - UIHelper.showUserFavorite(getActivity(), AppContext.getInstance() - .getLoginUid()); - break; - case R.id.rl_message: - UIHelper.showMyMes(getActivity()); - setNoticeReaded(); - break; - case R.id.rl_team: - UIHelper.showTeamMainActivity(getActivity()); - break; - case R.id.rl_blog: - UIHelper.showUserBlog(getActivity(), AppContext.getInstance() - .getLoginUid()); - break; - case R.id.rl_user_center: - UIHelper.showUserCenter(getActivity(), AppContext.getInstance() - .getLoginUid(), AppContext.getInstance().getLoginUser() - .getName()); - break; - default: - break; - } - } - - private void showMyQrCode() { - MyQrodeDialog dialog = new MyQrodeDialog(getActivity()); - dialog.show(); - } - - @Override - public void initData() {} - - private void setNoticeReaded() { - mMesCount.setText(""); - mMesCount.hide(); - } - -} diff --git a/app/src/main/java/net/oschina/app/fragment/MyInformationFragmentDetail.java b/app/src/main/java/net/oschina/app/fragment/MyInformationFragmentDetail.java index a47663fa47305d33bb23d0c2b27f0b452ba3432b..ba3adc99e5ca2fb240ece0f54d54f9ccd57d0f20 100644 --- a/app/src/main/java/net/oschina/app/fragment/MyInformationFragmentDetail.java +++ b/app/src/main/java/net/oschina/app/fragment/MyInformationFragmentDetail.java @@ -1,124 +1,70 @@ package net.oschina.app.fragment; -import android.app.Activity; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.os.Environment; -import android.provider.MediaStore; -import android.provider.MediaStore.Images; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; import android.widget.TextView; -import com.loopj.android.http.AsyncHttpResponseHandler; - -import net.oschina.app.AppContext; import net.oschina.app.R; -import net.oschina.app.api.remote.OSChinaApi; import net.oschina.app.base.BaseFragment; -import net.oschina.app.bean.MyInformation; -import net.oschina.app.bean.Result; -import net.oschina.app.bean.ResultBean; -import net.oschina.app.bean.User; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.ui.SimpleBackActivity; import net.oschina.app.ui.empty.EmptyLayout; -import net.oschina.app.util.DialogHelp; -import net.oschina.app.util.FileUtil; -import net.oschina.app.util.ImageUtils; import net.oschina.app.util.StringUtils; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; - -import org.kymjs.kjframe.Core; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.text.SimpleDateFormat; -import java.util.Date; +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; -import butterknife.OnClick; -import cz.msebera.android.httpclient.Header; /** * 登录用户信息详情 * * @author FireAnt(http://my.oschina.net/LittleDY) * @version 创建时间:2015年1月6日 上午10:33:18 + * updated by jzz on 2017/02/09 */ public class MyInformationFragmentDetail extends BaseFragment { - public static final int ACTION_TYPE_ALBUM = 0; - public static final int ACTION_TYPE_PHOTO = 1; + @Bind(R.id.iv_avatar) + PortraitView mUserFace; - @InjectView(R.id.iv_avatar) - ImageView mUserFace; + @Bind(R.id.identityView) + IdentityView identityView; - @InjectView(R.id.tv_name) + @Bind(R.id.tv_name) TextView mName; - @InjectView(R.id.tv_join_time) + @Bind(R.id.tv_join_time) TextView mJoinTime; - @InjectView(R.id.tv_location) + @Bind(R.id.tv_location) TextView mFrom; - @InjectView(R.id.tv_development_platform) + @Bind(R.id.tv_development_platform) TextView mPlatFrom; - @InjectView(R.id.tv_academic_focus) + @Bind(R.id.tv_academic_focus) TextView mFocus; - @InjectView(R.id.error_layout) - EmptyLayout mErrorLayout; - - private User mUser; - - private boolean isChangeFace = false; - - private String theLarge; - - private final static int CROP = 200; + @Bind(R.id.tv_desc) + TextView mDesc; - private final static String FILE_SAVEPATH = Environment - .getExternalStorageDirectory().getAbsolutePath() - + "/OSChina/Portrait/"; - private Uri origUri; - private Uri cropUri; - private File protraitFile; - private Bitmap protraitBitmap; - private String protraitPath; - - private final AsyncHttpResponseHandler mHandler = new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); - MyInformation user = XmlUtils.toBean(MyInformation.class, - new ByteArrayInputStream(arg2)); - if (user != null && user.getUser() != null) { - mUser = user.getUser(); - fillUI(); - } else { - this.onFailure(arg0, arg1, arg2, null); - } - } + @Bind(R.id.error_layout) + EmptyLayout mErrorLayout; - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) { - mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); - } + private User userInfo; - }; + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle arguments = getArguments(); + userInfo = (User) arguments.getSerializable("user_info"); + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -130,273 +76,42 @@ public class MyInformationFragmentDetail extends BaseFragment { return view; } - @Override - @OnClick({R.id.iv_avatar, R.id.btn_logout}) - public void onClick(View v) { - switch (v.getId()) { - case R.id.iv_avatar: - showClickAvatar(); - break; - case R.id.btn_logout: - AppContext.getInstance().Logout(); - AppContext.showToastShort(R.string.tip_logout_success); - getActivity().finish(); - break; - default: - break; - } - } - - public void showClickAvatar() { - if (mUser == null) { - AppContext.showToast(""); - return; - } - DialogHelp.getSelectDialog(getActivity(), "选择操作", getResources().getStringArray(R.array.avatar_option), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (i == 0) { - handleSelectPicture(); - } else { - UIHelper.showUserAvatar(getActivity(), - mUser.getPortrait()); - } - } - }).show(); - } - - private void handleSelectPicture() { - DialogHelp.getSelectDialog(getActivity(), "选择图片", getResources().getStringArray(R.array.choose_picture), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - goToSelectPicture(i); - } - }).show(); - } - - private void goToSelectPicture(int position) { - switch (position) { - case ACTION_TYPE_ALBUM: - startImagePick(); - break; - case ACTION_TYPE_PHOTO: - startTakePhoto(); - break; - default: - break; - } - } - @Override public void initView(View view) { - ButterKnife.inject(this, view); - mErrorLayout.setOnLayoutClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - sendRequiredData(); - } - }); - - mUserFace.setOnClickListener(this); + ButterKnife.bind(this, view); } @Override public void initData() { - sendRequiredData(); - } - - public void fillUI() { - Core.getKJBitmap().displayWithLoadBitmap(mUserFace, mUser.getPortrait(), - R.drawable.widget_dface); - mName.setText(mUser.getName()); - mJoinTime.setText(StringUtils.friendly_time(mUser.getJointime())); - mFrom.setText(mUser.getFrom()); - mPlatFrom.setText(mUser.getDevplatform()); - mFocus.setText(mUser.getExpertise()); - } - - public void sendRequiredData() { - mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); - OSChinaApi.getMyInformation(AppContext.getInstance().getLoginUid(), - mHandler); - } - - /** - * 上传新照片 - */ - private void uploadNewPhoto() { - showWaitDialog("正在上传头像..."); - - // 获取头像缩略图 - if (!StringUtils.isEmpty(protraitPath) && protraitFile.exists()) { - protraitBitmap = ImageUtils - .loadImgThumbnail(protraitPath, 200, 200); - } else { - AppContext.showToast("图像不存在,上传失败"); - } - if (protraitBitmap != null) { - - try { - OSChinaApi.updatePortrait(AppContext.getInstance() - .getLoginUid(), protraitFile, - new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int arg0, Header[] arg1, - byte[] arg2) { - Result res = XmlUtils.toBean(ResultBean.class, - new ByteArrayInputStream(arg2)) - .getResult(); - if (res.OK()) { - AppContext.showToast("更换成功"); - // 显示新头像 - mUserFace.setImageBitmap(protraitBitmap); - isChangeFace = true; - } else { - AppContext.showToast(res.getErrorMessage()); - } - } - - @Override - public void onFailure(int arg0, Header[] arg1, - byte[] arg2, Throwable arg3) { - AppContext.showToast("更换头像失败"); - } - - @Override - public void onFinish() { - hideWaitDialog(); - } - }); - } catch (FileNotFoundException e) { - AppContext.showToast("图像不存在,上传失败"); - } - } - } - - /** - * 选择图片裁剪 - */ - private void startImagePick() { - Intent intent; - if (Build.VERSION.SDK_INT < 19) { - intent = new Intent(); - intent.setAction(Intent.ACTION_GET_CONTENT); - intent.setType("image/*"); - startActivityForResult(Intent.createChooser(intent, "选择图片"), - ImageUtils.REQUEST_CODE_GETIMAGE_BYCROP); - } else { - intent = new Intent(Intent.ACTION_PICK, - Images.Media.EXTERNAL_CONTENT_URI); - intent.setType("image/*"); - startActivityForResult(Intent.createChooser(intent, "选择图片"), - ImageUtils.REQUEST_CODE_GETIMAGE_BYCROP); - } - } - - private void startTakePhoto() { - Intent intent; - // 判断是否挂载了SD卡 - String savePath = ""; - String storageState = Environment.getExternalStorageState(); - if (storageState.equals(Environment.MEDIA_MOUNTED)) { - savePath = Environment.getExternalStorageDirectory() - .getAbsolutePath() + "/oschina/Camera/"; - File savedir = new File(savePath); - if (!savedir.exists()) { - savedir.mkdirs(); - } - } - - // 没有挂载SD卡,无法保存文件 - if (StringUtils.isEmpty(savePath)) { - AppContext.showToastShort("无法保存照片,请检查SD卡是否挂载"); + if (userInfo == null) return; - } - - String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss") - .format(new Date()); - String fileName = "osc_" + timeStamp + ".jpg";// 照片命名 - File out = new File(savePath, fileName); - Uri uri = Uri.fromFile(out); - origUri = uri; - - theLarge = savePath + fileName;// 该照片的绝对路径 - - intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); - startActivityForResult(intent, - ImageUtils.REQUEST_CODE_GETIMAGE_BYCAMERA); - } - // 裁剪头像的绝对路径 - private Uri getUploadTempFile(Uri uri) { - String storageState = Environment.getExternalStorageState(); - if (storageState.equals(Environment.MEDIA_MOUNTED)) { - File savedir = new File(FILE_SAVEPATH); - if (!savedir.exists()) { - savedir.mkdirs(); - } - } else { - AppContext.showToast("无法保存上传的头像,请检查SD卡是否挂载"); - return null; + if (userInfo.getId() != AccountHelper.getUserId() + && getActivity() instanceof SimpleBackActivity) { + String title = TextUtils.isEmpty(userInfo.getName()) ? "" : userInfo.getName(); + ((SimpleBackActivity) getActivity()).setActionBarTitle(title); } - String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss") - .format(new Date()); - String thePath = ImageUtils.getAbsolutePathFromNoStandardUri(uri); - // 如果是标准Uri - if (StringUtils.isEmpty(thePath)) { - thePath = ImageUtils.getAbsoluteImagePath(getActivity(), uri); - } - String ext = FileUtil.getFileFormat(thePath); - ext = StringUtils.isEmpty(ext) ? "jpg" : ext; - // 照片命名 - String cropFileName = "osc_crop_" + timeStamp + "." + ext; - // 裁剪头像的绝对路径 - protraitPath = FILE_SAVEPATH + cropFileName; - protraitFile = new File(protraitPath); - - cropUri = Uri.fromFile(protraitFile); - return this.cropUri; + // sendRequiredData(); + fillUI(); } - /** - * 拍照后裁剪 - * - * @param data 原始图片 - */ - private void startActionCrop(Uri data) { - Intent intent = new Intent("com.android.camera.action.CROP"); - intent.setDataAndType(data, "image/*"); - intent.putExtra("output", this.getUploadTempFile(data)); - intent.putExtra("crop", "true"); - intent.putExtra("aspectX", 1);// 裁剪框比例 - intent.putExtra("aspectY", 1); - intent.putExtra("outputX", CROP);// 输出图片大小 - intent.putExtra("outputY", CROP); - intent.putExtra("scale", true);// 去黑边 - intent.putExtra("scaleUpIfNeeded", true);// 去黑边 - startActivityForResult(intent, - ImageUtils.REQUEST_CODE_GETIMAGE_BYSDCARD); + @SuppressWarnings("deprecation") + public void fillUI() { + identityView.setup(userInfo); + mUserFace.setup(userInfo); + mUserFace.setOnClickListener(null); + mName.setText(getText(userInfo.getName())); + mJoinTime.setText(getText(StringUtils.formatYearMonthDayNew(userInfo.getMore().getJoinDate()))); + mFrom.setText(getText(userInfo.getMore().getCity())); + mPlatFrom.setText(getText(userInfo.getMore().getPlatform())); + mFocus.setText(getText(userInfo.getMore().getExpertise())); + mDesc.setText(getText(userInfo.getDesc())); } - @Override - public void onActivityResult(final int requestCode, final int resultCode, - final Intent imageReturnIntent) { - if (resultCode != Activity.RESULT_OK) - return; - - switch (requestCode) { - case ImageUtils.REQUEST_CODE_GETIMAGE_BYCAMERA: - startActionCrop(origUri);// 拍照后裁剪 - break; - case ImageUtils.REQUEST_CODE_GETIMAGE_BYCROP: - startActionCrop(imageReturnIntent.getData());// 选图后裁剪 - break; - case ImageUtils.REQUEST_CODE_GETIMAGE_BYSDCARD: - uploadNewPhoto(); - break; - } + private String getText(String text) { + if (text == null || text.equalsIgnoreCase("null")) + return "<无>"; + else return text; } } diff --git a/app/src/main/java/net/oschina/app/fragment/NewsDetailFragment.java b/app/src/main/java/net/oschina/app/fragment/NewsDetailFragment.java deleted file mode 100644 index b574db747d519f9d5998c7a25d598a5300dbea1f..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/NewsDetailFragment.java +++ /dev/null @@ -1,127 +0,0 @@ -package net.oschina.app.fragment; - -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.CommonDetailFragment; -import net.oschina.app.bean.CommentList; -import net.oschina.app.bean.FavoriteList; -import net.oschina.app.bean.News; -import net.oschina.app.bean.NewsDetail; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.ThemeSwitchUtils; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; - -import java.io.InputStream; - -/** - * Created by 火蚁 on 15/5/25. - */ -public class NewsDetailFragment extends CommonDetailFragment { - - @Override - protected String getCacheKey() { - return "news_" + mId; - } - - @Override - protected void sendRequestDataForNet() { - OSChinaApi.getNewsDetail(mId, mDetailHeandler); - } - - @Override - protected News parseData(InputStream is) { - return XmlUtils.toBean(NewsDetail.class, is).getNews(); - } - - @Override - protected String getWebViewBody(News detail) { - StringBuffer body = new StringBuffer(); - body.append(UIHelper.WEB_STYLE).append(UIHelper.WEB_LOAD_IMAGES); - body.append(ThemeSwitchUtils.getWebViewBodyString()); - // 添加title - body.append(String.format("
    %s
    ", mDetail.getTitle())); - // 添加作者和时间 - String time = StringUtils.friendly_time(mDetail.getPubDate()); - String author = String.format("%s", mDetail.getAuthorId(), mDetail.getAuthor()); - body.append(String.format("
    %s    %s
    ", author, time)); - // 添加图片点击放大支持 - body.append(UIHelper.setHtmlCotentSupportImagePreview(mDetail.getBody())); - - - // 更多关于***软件的信息 - String softwareName = mDetail.getSoftwareName(); - String softwareLink = mDetail.getSoftwareLink(); - if (!StringUtils.isEmpty(softwareName) - && !StringUtils.isEmpty(softwareLink)) - body.append(String - .format("
    更多关于: %s 的详细信息
    ", - softwareLink, softwareName)); - - // 相关新闻 - if (mDetail != null && mDetail.getRelatives() != null - && mDetail.getRelatives().size() > 0) { - String strRelative = ""; - for (News.Relative relative : mDetail.getRelatives()) { - strRelative += String.format( - "
  • %s
  • ", - relative.url, relative.title); - } - body.append("

    " - + String.format("
    相关资讯
      %s
    ", - strRelative)); - } - body.append("
    "); - // 封尾 - body.append("
    "); - return body.toString(); - } - - @Override - protected void showCommentView() { - if (mDetail != null) - UIHelper.showComment(getActivity(), mId, - CommentList.CATALOG_NEWS); - } - - @Override - protected int getCommentType() { - return CommentList.CATALOG_NEWS; - } - - @Override - protected int getFavoriteTargetType() { - return FavoriteList.TYPE_NEWS; - } - - @Override - protected String getShareTitle() { - return mDetail.getTitle(); - } - - @Override - protected String getShareContent() { - return StringUtils.getSubString(0, 55, - getFilterHtmlBody(mDetail.getBody())); - } - - @Override - protected String getShareUrl() { - return mDetail.getUrl().replace("http://www", "http://m"); - } - - @Override - protected int getFavoriteState() { - return mDetail.getFavorite(); - } - - @Override - protected void updateFavoriteChanged(int newFavoritedState) { - mDetail.setFavorite(newFavoritedState); - saveCache(mDetail); - } - - @Override - protected int getCommentCount() { - return mDetail.getCommentCount(); - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/NewsFragment.java b/app/src/main/java/net/oschina/app/fragment/NewsFragment.java deleted file mode 100644 index c52d3a57b28ed372c12e58da0dd65832d833ba4c..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/NewsFragment.java +++ /dev/null @@ -1,106 +0,0 @@ -package net.oschina.app.fragment; - -import java.io.InputStream; -import java.io.Serializable; -import java.util.List; - -import net.oschina.app.adapter.NewsAdapter; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.News; -import net.oschina.app.bean.NewsList; -import net.oschina.app.interf.OnTabReselectListener; -import net.oschina.app.ui.empty.EmptyLayout; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; -import android.view.View; -import android.widget.AdapterView; - -/** - * 新闻资讯 - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年11月12日 下午4:17:45 - * - */ -public class NewsFragment extends BaseListFragment implements - OnTabReselectListener { - - protected static final String TAG = NewsFragment.class.getSimpleName(); - private static final String CACHE_KEY_PREFIX = "newslist_"; - - @Override - protected NewsAdapter getListAdapter() { - return new NewsAdapter(); - } - - @Override - protected String getCacheKeyPrefix() { - return CACHE_KEY_PREFIX + mCatalog; - } - - @Override - protected NewsList parseList(InputStream is) throws Exception { - NewsList list = null; - try { - list = XmlUtils.toBean(NewsList.class, is); - } catch (NullPointerException e) { - list = new NewsList(); - } - return list; - } - - @Override - protected NewsList readList(Serializable seri) { - return ((NewsList) seri); - } - - @Override - protected void sendRequestData() { - OSChinaApi.getNewsList(mCatalog, mCurrentPage, mHandler); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - News news = mAdapter.getItem(position); - if (news != null) { - UIHelper.showNewsRedirect(view.getContext(), news); - - // 放入已读列表 - saveToReadedList(view, NewsList.PREF_READED_NEWS_LIST, news.getId() - + ""); - } - } - - @Override - protected void executeOnLoadDataSuccess(List data) { - if (mCatalog == NewsList.CATALOG_WEEK - || mCatalog == NewsList.CATALOG_MONTH) { - mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); - if (mState == STATE_REFRESH) - mAdapter.clear(); - mAdapter.addData(data); - mState = STATE_NOMORE; - mAdapter.setState(ListBaseAdapter.STATE_NO_MORE); - return; - } - super.executeOnLoadDataSuccess(data); - } - - @Override - public void onTabReselect() { - onRefresh(); - } - - @Override - protected long getAutoRefreshTime() { - // 最新资讯两小时刷新一次 - if (mCatalog == NewsList.CATALOG_ALL) { - - return 2 * 60 * 60; - } - return super.getAutoRefreshTime(); - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/PostDetailFragment.java b/app/src/main/java/net/oschina/app/fragment/PostDetailFragment.java deleted file mode 100644 index fe4e6ede8de76602f0b71c4333755ac5e67b18b2..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/PostDetailFragment.java +++ /dev/null @@ -1,129 +0,0 @@ -package net.oschina.app.fragment; - -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.CommonDetailFragment; -import net.oschina.app.bean.CommentList; -import net.oschina.app.bean.FavoriteList; -import net.oschina.app.bean.Post; -import net.oschina.app.bean.PostDetail; -import net.oschina.app.ui.DetailActivity; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.ThemeSwitchUtils; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.URLsUtils; -import net.oschina.app.util.XmlUtils; - -import java.io.InputStream; -import java.net.URLEncoder; - -/** - * Created by 火蚁 on 15/5/25. - */ -public class PostDetailFragment extends CommonDetailFragment { - @Override - protected String getCacheKey() { - return "post_" + mId; - } - - @Override - protected void sendRequestDataForNet() { - OSChinaApi.getPostDetail(mId, mDetailHeandler); - } - - @Override - protected Post parseData(InputStream is) { - return XmlUtils.toBean(PostDetail.class, is).getPost(); - } - - @Override - protected String getWebViewBody(Post detail) { - StringBuffer body = new StringBuffer(); - body.append(UIHelper.WEB_STYLE).append(UIHelper.WEB_LOAD_IMAGES); - body.append(ThemeSwitchUtils.getWebViewBodyString()); - // 添加title - body.append(String.format("
    %s
    ", mDetail.getTitle())); - // 添加作者和时间 - String time = StringUtils.friendly_time(mDetail.getPubDate()); - String author = String.format("%s", mDetail.getAuthorId(), mDetail.getAuthor()); - body.append(String.format("
    %s    %s
    ", author, time)); - // 添加图片点击放大支持 - body.append(UIHelper.setHtmlCotentSupportImagePreview(mDetail.getBody())); - body.append(getPostTags(mDetail.getTags())); - // 封尾 - body.append(""); - return body.toString(); - } - - @SuppressWarnings("deprecation") - private String getPostTags(Post.Tags taglist) { - if (taglist == null) - return ""; - StringBuffer tags = new StringBuffer(); - for (String tag : taglist.getTags()) { - tags.append(String - .format(" %s   ", - URLEncoder.encode(tag), tag)); - } - return String.format("
    %s
    ", tags); - } - - @Override - protected void showCommentView() { - if (mDetail != null) { - UIHelper.showComment(getActivity(), mId, CommentList.CATALOG_POST); - } - } - - @Override - protected int getCommentType() { - return CommentList.CATALOG_POST; - } - - @Override - protected String getShareTitle() { - return mDetail.getTitle(); - } - - @Override - protected String getShareContent() { - return StringUtils.getSubString(0, 55, - getFilterHtmlBody(mDetail.getBody())); - } - - @Override - protected String getShareUrl() { - return String.format(URLsUtils.URL_MOBILE + "question/%s_%s", mDetail.getAuthorId(), mId); - } - - @Override - protected int getFavoriteTargetType() { - return FavoriteList.TYPE_POST; - } - - @Override - protected int getFavoriteState() { - return mDetail.getFavorite(); - } - - @Override - protected void updateFavoriteChanged(int newFavoritedState) { - mDetail.setFavorite(newFavoritedState); - saveCache(mDetail); - } - - @Override - protected int getCommentCount() { - return mDetail.getAnswerCount(); - } - - @Override - public void onResume() { - super.onResume(); - ((DetailActivity) getActivity()).toolFragment.showReportButton(); - } - - @Override - protected String getRepotrUrl() { - return mDetail.getUrl(); - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/PostsFragment.java b/app/src/main/java/net/oschina/app/fragment/PostsFragment.java deleted file mode 100644 index 03939793102bc4dbbc19ea2af392358201c90f57..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/PostsFragment.java +++ /dev/null @@ -1,64 +0,0 @@ -package net.oschina.app.fragment; - -import java.io.InputStream; -import java.io.Serializable; - -import net.oschina.app.adapter.PostAdapter; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.bean.Post; -import net.oschina.app.bean.PostList; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; -import android.view.View; -import android.widget.AdapterView; - -/** - * 问答 - * - * @author william_sim - */ -public class PostsFragment extends BaseListFragment { - - protected static final String TAG = PostsFragment.class.getSimpleName(); - private static final String CACHE_KEY_PREFIX = "postslist_"; - - @Override - protected PostAdapter getListAdapter() { - return new PostAdapter(); - } - - @Override - protected String getCacheKeyPrefix() { - return CACHE_KEY_PREFIX + mCatalog; - } - - @Override - protected PostList parseList(InputStream is) throws Exception { - PostList list = XmlUtils.toBean(PostList.class, is); - return list; - } - - @Override - protected PostList readList(Serializable seri) { - return ((PostList) seri); - } - - @Override - protected void sendRequestData() { - OSChinaApi.getPostList(mCatalog, mCurrentPage, mHandler); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Post post = mAdapter.getItem(position); - if (post != null) { - UIHelper.showPostDetail(view.getContext(), post.getId(), - post.getAnswerCount()); - // 放入已读列表 - saveToReadedList(view, PostList.PREF_READED_POST_LIST, post.getId() - + ""); - } - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/QuestionTagFragment.java b/app/src/main/java/net/oschina/app/fragment/QuestionTagFragment.java index 7f002b30d48a1f68b2608115ebd1a711dc2fe156..5fe46042474f919d8949375b850bed30bfd576cf 100644 --- a/app/src/main/java/net/oschina/app/fragment/QuestionTagFragment.java +++ b/app/src/main/java/net/oschina/app/fragment/QuestionTagFragment.java @@ -1,7 +1,8 @@ package net.oschina.app.fragment; -import java.io.InputStream; -import java.io.Serializable; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; import net.oschina.app.R; import net.oschina.app.adapter.PostAdapter; @@ -12,16 +13,14 @@ import net.oschina.app.bean.Post; import net.oschina.app.bean.PostList; import net.oschina.app.util.UIHelper; import net.oschina.app.util.XmlUtils; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; + +import java.io.InputStream; +import java.io.Serializable; /** * 标签相关帖子 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年11月6日 下午3:39:07 - * */ public class QuestionTagFragment extends BaseListFragment { @@ -49,13 +48,12 @@ public class QuestionTagFragment extends BaseListFragment { @Override protected String getCacheKeyPrefix() { - return new StringBuffer(CACHE_KEY_PREFIX).append(mTag).toString(); + return CACHE_KEY_PREFIX + mTag; } @Override protected PostList parseList(InputStream is) throws Exception { - PostList list = XmlUtils.toBean(PostList.class, is); - return list; + return XmlUtils.toBean(PostList.class, is); } @Override @@ -70,7 +68,7 @@ public class QuestionTagFragment extends BaseListFragment { @Override public void onItemClick(AdapterView parent, View view, int position, - long id) { + long id) { Post post = mAdapter.getItem(position); if (post != null) UIHelper.showPostDetail(view.getContext(), post.getId(), diff --git a/app/src/main/java/net/oschina/app/fragment/SearchFragment.java b/app/src/main/java/net/oschina/app/fragment/SearchFragment.java deleted file mode 100644 index 3b9055ab075b969114f9977a5fa80cfc89edc8a6..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/SearchFragment.java +++ /dev/null @@ -1,93 +0,0 @@ -package net.oschina.app.fragment; - -import android.os.Bundle; -import android.view.View; -import android.view.WindowManager; -import android.widget.AdapterView; - -import net.oschina.app.adapter.SearchAdapter; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.bean.SearchList; -import net.oschina.app.bean.SearchResult; -import net.oschina.app.ui.empty.EmptyLayout; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; - -import java.io.InputStream; -import java.io.Serializable; - -public class SearchFragment extends BaseListFragment { - protected static final String TAG = SearchFragment.class.getSimpleName(); - private static final String CACHE_KEY_PREFIX = "search_list_"; - private String mCatalog; - private String mSearch; - private boolean mRquestDataIfCreated = false; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Bundle args = getArguments(); - if (args != null) { - mCatalog = args.getString(BUNDLE_KEY_CATALOG); - } - int mode = WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN - | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; - getActivity().getWindow().setSoftInputMode(mode); - } - - public void search(String search) { - mSearch = search; - if (mErrorLayout != null) { - mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); - mState = STATE_REFRESH; - requestData(true); - } else { - mRquestDataIfCreated = true; - } - } - - @Override - protected boolean requestDataIfViewCreated() { - return mRquestDataIfCreated; - } - - @Override - protected SearchAdapter getListAdapter() { - return new SearchAdapter(); - } - - @Override - protected String getCacheKeyPrefix() { - return CACHE_KEY_PREFIX + mCatalog + mSearch; - } - - @Override - protected SearchList parseList(InputStream is) throws Exception { - SearchList list = XmlUtils.toBean(SearchList.class, is); - return list; - } - - @Override - protected SearchList readList(Serializable seri) { - return ((SearchList) seri); - } - - @Override - protected void sendRequestData() { - OSChinaApi.getSearchList(mCatalog, mSearch, mCurrentPage, mHandler); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - SearchResult res = mAdapter.getItem(position); - if (res != null) { - if (res.getType().equalsIgnoreCase(SearchList.CATALOG_SOFTWARE)) { - UIHelper.showSoftwareDetailById(getActivity(), res.getId()); - } else { - UIHelper.showUrlRedirect(getActivity(), res.getUrl()); - } - } - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/SettingsFragment.java b/app/src/main/java/net/oschina/app/fragment/SettingsFragment.java index 9df9372bdad1428eb4482779bf3a4fcba06efad1..e9fa3ed8d9359a14ee1387e95f1e3b18f8135c54 100644 --- a/app/src/main/java/net/oschina/app/fragment/SettingsFragment.java +++ b/app/src/main/java/net/oschina/app/fragment/SettingsFragment.java @@ -1,54 +1,75 @@ package net.oschina.app.fragment; +import android.Manifest; +import android.annotation.SuppressLint; import android.content.DialogInterface; +import android.content.Intent; import android.os.Bundle; +import android.provider.Settings; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; import android.widget.TextView; import net.oschina.app.AppConfig; import net.oschina.app.AppContext; -import net.oschina.app.AppManager; import net.oschina.app.R; import net.oschina.app.base.BaseFragment; -import net.oschina.app.util.DialogHelp; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.bean.Version; +import net.oschina.app.improve.main.FeedBackActivity; +import net.oschina.app.improve.main.update.CheckUpdateManager; +import net.oschina.app.improve.main.update.DownloadService; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.SystemConfigView; +import net.oschina.app.improve.widget.togglebutton.ToggleButton; +import net.oschina.app.improve.widget.togglebutton.ToggleButton.OnToggleChanged; import net.oschina.app.util.FileUtil; import net.oschina.app.util.MethodsCompat; import net.oschina.app.util.UIHelper; -import net.oschina.app.widget.togglebutton.ToggleButton; -import net.oschina.app.widget.togglebutton.ToggleButton.OnToggleChanged; - -import org.kymjs.kjframe.http.HttpConfig; import java.io.File; +import java.util.List; +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; /** * 系统设置界面 * * @author kymjs */ -public class SettingsFragment extends BaseFragment { +public class SettingsFragment extends BaseFragment implements EasyPermissions.PermissionCallbacks, CheckUpdateManager.RequestPermissions { + + private static final int RC_EXTERNAL_STORAGE = 0x04;//存储权限 - @InjectView(R.id.tb_loading_img) - ToggleButton mTbLoadImg; - @InjectView(R.id.tv_cache_size) + @Bind(R.id.tv_cache_size) TextView mTvCacheSize; - @InjectView(R.id.setting_logout) - TextView mTvExit; - @InjectView(R.id.tb_double_click_exit) + @Bind(R.id.rl_check_version) + FrameLayout mRlCheck_version; + @Bind(R.id.tb_double_click_exit) ToggleButton mTbDoubleClickExit; + @Bind(R.id.setting_line_top) + View mSettingLineTop; + @Bind(R.id.setting_line_bottom) + View mSettingLineBottom; + @Bind(R.id.rl_cancel) + FrameLayout mCancel; + + private Version mVersion; @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_settings, container, false); - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); initView(view); initData(); return view; @@ -56,12 +77,6 @@ public class SettingsFragment extends BaseFragment { @Override public void initView(View view) { - mTbLoadImg.setOnToggleChanged(new OnToggleChanged() { - @Override - public void onToggle(boolean on) { - AppContext.setLoadImage(on); - } - }); mTbDoubleClickExit.setOnToggleChanged(new OnToggleChanged() { @Override @@ -70,40 +85,46 @@ public class SettingsFragment extends BaseFragment { } }); - view.findViewById(R.id.rl_loading_img).setOnClickListener(this); - view.findViewById(R.id.rl_notification_settings).setOnClickListener( - this); view.findViewById(R.id.rl_clean_cache).setOnClickListener(this); view.findViewById(R.id.rl_double_click_exit).setOnClickListener(this); view.findViewById(R.id.rl_about).setOnClickListener(this); - view.findViewById(R.id.rl_exit).setOnClickListener(this); + view.findViewById(R.id.rl_check_version).setOnClickListener(this); + // view.findViewById(R.id.rl_exit).setOnClickListener(this); + view.findViewById(R.id.rl_feedback).setOnClickListener(this); + mCancel.setOnClickListener(this); - if (!AppContext.getInstance().isLogin()) { - mTvExit.setText("退出"); - } + SystemConfigView.show((ViewGroup) view.findViewById(R.id.lay_linear)); } @Override public void initData() { - if (AppContext.get(AppConfig.KEY_LOAD_IMAGE, true)) { - mTbLoadImg.setToggleOn(); - } else { - mTbLoadImg.setToggleOff(); - } - if (AppContext.get(AppConfig.KEY_DOUBLE_CLICK_EXIT, true)) { mTbDoubleClickExit.setToggleOn(); } else { mTbDoubleClickExit.setToggleOff(); } + calculateCacheSize(); + } - caculateCacheSize(); + @Override + public void onResume() { + super.onResume(); + boolean login = AccountHelper.isLogin(); + if (!login) { + mCancel.setVisibility(View.INVISIBLE); + mSettingLineTop.setVisibility(View.INVISIBLE); + mSettingLineBottom.setVisibility(View.INVISIBLE); + } else { + mCancel.setVisibility(View.VISIBLE); + mSettingLineTop.setVisibility(View.VISIBLE); + mSettingLineBottom.setVisibility(View.VISIBLE); + } } /** * 计算缓存的大小 */ - private void caculateCacheSize() { + private void calculateCacheSize() { long fileSize = 0; String cacheSize = "0KB"; File filesDir = getActivity().getFilesDir(); @@ -116,9 +137,6 @@ public class SettingsFragment extends BaseFragment { File externalCacheDir = MethodsCompat .getExternalCacheDir(getActivity()); fileSize += FileUtil.getDirSize(externalCacheDir); - fileSize += FileUtil.getDirSize(new File( - org.kymjs.kjframe.utils.FileUtils.getSDCardPath() - + File.separator + HttpConfig.CACHEPATH)); } if (fileSize > 0) cacheSize = FileUtil.formatFileSize(fileSize); @@ -129,23 +147,42 @@ public class SettingsFragment extends BaseFragment { public void onClick(View v) { final int id = v.getId(); switch (id) { - case R.id.rl_loading_img: - mTbLoadImg.toggle(); - break; - case R.id.rl_notification_settings: - UIHelper.showSettingNotification(getActivity()); - break; case R.id.rl_clean_cache: onClickCleanCache(); break; case R.id.rl_double_click_exit: mTbDoubleClickExit.toggle(); break; + case R.id.rl_feedback: + //UIHelper.showSimpleBack(getActivity(), SimpleBackPage.FEED_BACK); + if (!AccountHelper.isLogin()) { + LoginActivity.show(getContext()); + return; + } + FeedBackActivity.show(getActivity()); + break; case R.id.rl_about: UIHelper.showAboutOSC(getActivity()); break; - case R.id.rl_exit: - onClickExit(); + case R.id.rl_check_version: + onClickUpdate(); + break; + case R.id.rl_cancel: + // 清理所有缓存 + UIHelper.clearAppCache(false); + // 注销操作 + AccountHelper.logout(mCancel, new Runnable() { + @SuppressLint("SetTextI18n") + @Override + public void run() { + //getActivity().finish(); + mTvCacheSize.setText("0KB"); + AppContext.showToastShort(getString(R.string.logout_success_hint)); + mCancel.setVisibility(View.INVISIBLE); + mSettingLineTop.setVisibility(View.INVISIBLE); + mSettingLineBottom.setVisibility(View.INVISIBLE); + } + }); break; default: break; @@ -153,22 +190,59 @@ public class SettingsFragment extends BaseFragment { } + private void onClickUpdate() { + CheckUpdateManager manager = new CheckUpdateManager(getActivity(), true); + manager.setCaller(this); + manager.checkUpdate(true); + } + private void onClickCleanCache() { - DialogHelp.getConfirmDialog(getActivity(), "是否清空缓存?", new DialogInterface.OnClickListener + DialogHelper.getConfirmDialog(getActivity(), "是否清空缓存?", new DialogInterface.OnClickListener () { + @SuppressLint("SetTextI18n") @Override public void onClick(DialogInterface dialogInterface, int i) { - UIHelper.clearAppCache(getActivity()); + UIHelper.clearAppCache(true); mTvCacheSize.setText("0KB"); } }).show(); } - private void onClickExit() { - AppContext - .set(AppConfig.KEY_NOTIFICATION_DISABLE_WHEN_EXIT, - false); - AppManager.getAppManager().AppExit(getActivity()); - getActivity().finish(); + @Override + public void call(Version version) { + this.mVersion = version; + requestExternalStorage(); + } + + @SuppressLint("InlinedApi") + @AfterPermissionGranted(RC_EXTERNAL_STORAGE) + public void requestExternalStorage() { + if (EasyPermissions.hasPermissions(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)) { + DownloadService.startService(getActivity(), mVersion.getDownloadUrl()); + } else { + EasyPermissions.requestPermissions(this, "", RC_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE); + } + } + + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + DialogHelper.getConfirmDialog(getActivity(), "温馨提示", "需要开启开源中国对您手机的存储权限才能下载安装,是否现在开启", "去开启", "取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + } + }).show(); + } + + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); } } diff --git a/app/src/main/java/net/oschina/app/fragment/SettingsNotificationFragment.java b/app/src/main/java/net/oschina/app/fragment/SettingsNotificationFragment.java deleted file mode 100644 index 11a22e4d6338fe84d48e2a5b2d7f958e99c5d803..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/SettingsNotificationFragment.java +++ /dev/null @@ -1,90 +0,0 @@ -package net.oschina.app.fragment; - -import net.oschina.app.AppConfig; -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.base.BaseFragment; -import net.oschina.app.widget.togglebutton.ToggleButton; -import net.oschina.app.widget.togglebutton.ToggleButton.OnToggleChanged; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import butterknife.ButterKnife; -import butterknife.InjectView; - -public class SettingsNotificationFragment extends BaseFragment { - - @InjectView(R.id.tb_accept) ToggleButton mTbAccept; - @InjectView(R.id.tb_voice) ToggleButton mTbVoice; - @InjectView(R.id.tb_vibration) ToggleButton mTbVibration; - @InjectView(R.id.tb_app_exit) ToggleButton mTbAppExit; - - @Override - public View onCreateView(LayoutInflater inflater, - @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_settings_notifcation, container, - false); - ButterKnife.inject(this, view); - initView(view); - initData(); - return view; - } - - @Override - public void initView(View view) { - setToggleChanged(mTbAccept, AppConfig.KEY_NOTIFICATION_ACCEPT); - setToggleChanged(mTbVoice, AppConfig.KEY_NOTIFICATION_SOUND); - setToggleChanged(mTbVibration, AppConfig.KEY_NOTIFICATION_VIBRATION); - setToggleChanged(mTbAppExit, AppConfig.KEY_NOTIFICATION_DISABLE_WHEN_EXIT); - - view.findViewById(R.id.rl_accept).setOnClickListener(this); - view.findViewById(R.id.rl_voice).setOnClickListener(this); - view.findViewById(R.id.rl_vibration).setOnClickListener(this); - view.findViewById(R.id.rl_app_exit).setOnClickListener(this); - } - - public void initData() { - setToggle(AppContext.get(AppConfig.KEY_NOTIFICATION_ACCEPT, true), mTbAccept); - setToggle(AppContext.get(AppConfig.KEY_NOTIFICATION_SOUND, true), mTbVoice); - setToggle(AppContext.get(AppConfig.KEY_NOTIFICATION_VIBRATION, true), mTbVibration); - setToggle(AppContext.get(AppConfig.KEY_NOTIFICATION_DISABLE_WHEN_EXIT, true), mTbAppExit); - } - - private void setToggleChanged(ToggleButton tb, final String key) { - tb.setOnToggleChanged(new OnToggleChanged() { - - @Override - public void onToggle(boolean on) { - AppContext.set(key, on); - } - }); - } - - private void setToggle(boolean value, ToggleButton tb) { - if (value) - tb.setToggleOn(); - else - tb.setToggleOff(); - } - - @Override - public void onClick(View v) { - final int id = v.getId(); - switch (id) { - case R.id.rl_accept: - mTbAccept.toggle(); - break; - case R.id.rl_voice: - mTbVoice.toggle(); - break; - case R.id.rl_vibration: - mTbVibration.toggle(); - break; - case R.id.rl_app_exit: - mTbAppExit.toggle(); - break; - } - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/SoftWareTweetsFrament.java b/app/src/main/java/net/oschina/app/fragment/SoftWareTweetsFrament.java deleted file mode 100644 index 067b922ac107e5a6250907fa78df63a61db1c00f..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/SoftWareTweetsFrament.java +++ /dev/null @@ -1,116 +0,0 @@ -package net.oschina.app.fragment; - -import java.io.InputStream; -import java.io.Serializable; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.adapter.TweetAdapter; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseActivity; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.bean.Tweet; -import net.oschina.app.bean.TweetsList; -import net.oschina.app.service.ServerTaskUtils; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; -import android.app.Activity; -import android.os.Bundle; -import android.view.View; -import android.view.WindowManager; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemLongClickListener; - -public class SoftWareTweetsFrament extends BaseListFragment implements - OnItemLongClickListener { - - public static final String BUNDLE_KEY_ID = "BUNDLE_KEY_ID"; - protected static final String TAG = SoftWareTweetsFrament.class - .getSimpleName(); - private static final String CACHE_KEY_PREFIX = "software_tweet_list"; - - private int mId; - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - BaseActivity act = ((BaseActivity) activity); - try { - activity.findViewById(R.id.emoji_container).setVisibility( - View.VISIBLE); - } catch (NullPointerException e) { - } - } - - protected int getLayoutRes() { - return R.layout.fragment_pull_refresh_listview; - } - - @Override - public void initView(View view) { - super.initView(view); - mListView.setOnItemLongClickListener(this); - } - - @Override - public void onCreate(android.os.Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Bundle args = getArguments(); - if (args != null) { - mId = args.getInt(BUNDLE_KEY_ID, 0); - } - - int mode = WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN - | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - getActivity().getWindow().setSoftInputMode(mode); - } - - @Override - protected TweetAdapter getListAdapter() { - return new TweetAdapter(); - } - - @Override - protected String getCacheKeyPrefix() { - return new StringBuilder(CACHE_KEY_PREFIX).append("_").append(mId) - .toString(); - } - - @Override - protected TweetsList parseList(InputStream is) throws Exception { - return XmlUtils.toBean(TweetsList.class, is); - } - - @Override - protected TweetsList readList(Serializable seri) { - return ((TweetsList) seri); - } - - @Override - protected void sendRequestData() { - OSChinaApi.getSoftTweetList(mId, mCurrentPage, mHandler); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - final Tweet tweet = mAdapter.getItem(position); - if (tweet == null) { - return; - } - UIHelper.showTweetDetail(parent.getContext(), tweet, tweet.getId()); - } - - private void handleComment(String text) { - Tweet tweet = new Tweet(); - tweet.setAuthorid(AppContext.getInstance().getLoginUid()); - tweet.setBody(text); - ServerTaskUtils.pubSoftWareTweet(getActivity(), tweet, mId); - } - - @Override - public boolean onItemLongClick(AdapterView parent, View view, - int position, long id) { - return true; - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/SoftwareCatalogListFragment.java b/app/src/main/java/net/oschina/app/fragment/SoftwareCatalogListFragment.java index d2eae81525915956a15fc1ae620a2ad254050734..e21858e89071c254232a591d3152076359b8dc59 100644 --- a/app/src/main/java/net/oschina/app/fragment/SoftwareCatalogListFragment.java +++ b/app/src/main/java/net/oschina/app/fragment/SoftwareCatalogListFragment.java @@ -1,7 +1,17 @@ package net.oschina.app.fragment; -import java.io.ByteArrayInputStream; -import java.util.List; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AbsListView.OnScrollListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; + +import com.loopj.android.http.AsyncHttpResponseHandler; import net.oschina.app.R; import net.oschina.app.adapter.SoftwareAdapter; @@ -19,311 +29,302 @@ import net.oschina.app.util.UIHelper; import net.oschina.app.util.XmlUtils; import net.oschina.app.widget.ScrollLayout; -import cz.msebera.android.httpclient.Header; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ListView; +import java.io.ByteArrayInputStream; +import java.util.List; -import com.loopj.android.http.AsyncHttpResponseHandler; +import cz.msebera.android.httpclient.Header; public class SoftwareCatalogListFragment extends BaseFragment implements - OnItemClickListener, OnScrollListener { - protected static final int STATE_NONE = 0; - protected static final int STATE_REFRESH = 1; - protected static final int STATE_LOADMORE = 2; - - private final static int SCREEN_CATALOG = 0; - private final static int SCREEN_TAG = 1; - private final static int SCREEN_SOFTWARE = 2; - - private static ScrollLayout mScrollLayout; - private static ListView mLvCatalog, mLvTag, mLvSoftware; - private static EmptyLayout mEmptyView; - private static SoftwareCatalogListAdapter mCatalogAdapter, mTagAdapter; - private static SoftwareAdapter mSoftwareAdapter; - private static int mState = STATE_NONE; - private static int curScreen = SCREEN_CATALOG;// 默认当前屏幕 - private static int mCurrentTag; - private static int mCurrentPage; - - private AsyncHttpResponseHandler mCatalogHandler = new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - try { - SoftwareCatalogList list = XmlUtils.toBean( - SoftwareCatalogList.class, new ByteArrayInputStream( - arg2)); - if (mState == STATE_REFRESH) - mCatalogAdapter.clear(); - List data = list.getSoftwarecataloglist(); - mCatalogAdapter.addData(data); - mEmptyView.setErrorType(EmptyLayout.HIDE_LAYOUT); - if (data.size() == 0 && mState == STATE_REFRESH) { - mEmptyView.setErrorType(EmptyLayout.NODATA); - } else { - mCatalogAdapter - .setState(ListBaseAdapter.STATE_LESS_ONE_PAGE); - } - } catch (Exception e) { - e.printStackTrace(); - onFailure(arg0, arg1, arg2, e); - } - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) { - mEmptyView.setErrorType(EmptyLayout.NETWORK_ERROR); - } - - public void onFinish() { - mState = STATE_NONE; - } - }; - - private AsyncHttpResponseHandler mTagHandler = new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - try { - SoftwareCatalogList list = XmlUtils.toBean( - SoftwareCatalogList.class, new ByteArrayInputStream( - arg2)); - if (mState == STATE_REFRESH) - mTagAdapter.clear(); - List data = list.getSoftwarecataloglist(); - mTagAdapter.addData(data); - mEmptyView.setErrorType(EmptyLayout.HIDE_LAYOUT); - if (data.size() == 0 && mState == STATE_REFRESH) { - mEmptyView.setErrorType(EmptyLayout.NODATA); - } else { - mTagAdapter.setState(ListBaseAdapter.STATE_LESS_ONE_PAGE); - } - } catch (Exception e) { - e.printStackTrace(); - onFailure(arg0, arg1, arg2, e); - } - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) { - mEmptyView.setErrorType(EmptyLayout.NETWORK_ERROR); - } - - public void onFinish() { - mState = STATE_NONE; - } - }; - - private AsyncHttpResponseHandler mSoftwareHandler = new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int statusCode, Header[] headers, - byte[] responseBytes) { - try { - SoftwareList list = XmlUtils.toBean(SoftwareList.class, - new ByteArrayInputStream(responseBytes)); - executeOnLoadDataSuccess(list.getSoftwarelist()); - - } catch (Exception e) { - e.printStackTrace(); - onFailure(statusCode, headers, responseBytes, null); - } - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) { - mEmptyView.setErrorType(EmptyLayout.NETWORK_ERROR); - } - - public void onFinish() { - mState = STATE_NONE; - } - }; - - private OnItemClickListener mCatalogOnItemClick = new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - SoftwareType type = (SoftwareType) mCatalogAdapter - .getItem(position); - if (type != null && type.getTag() > 0) { - // 加载二级分类 - curScreen = SCREEN_TAG; - mScrollLayout.scrollToScreen(curScreen); - mCurrentTag = type.getTag(); - sendRequestCatalogData(mTagHandler); - } - } - }; - - private OnItemClickListener mTagOnItemClick = new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - SoftwareType type = (SoftwareType) mTagAdapter.getItem(position); - if (type != null && type.getTag() > 0) { - // 加载二级分类里面的软件列表 - curScreen = SCREEN_SOFTWARE; - mScrollLayout.scrollToScreen(curScreen); - mCurrentTag = type.getTag(); - mState = STATE_REFRESH; - sendRequestTagData(); - } - } - }; - - @Override - public View onCreateView(LayoutInflater inflater, - @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_opensoftware, container, - false); - initViews(view); - return view; - } - - private void initViews(View view) { - mScrollLayout = (ScrollLayout) view.findViewById(R.id.scrolllayout); - mScrollLayout.setIsScroll(false); - - mEmptyView = (EmptyLayout) view.findViewById(R.id.error_layout); - mLvCatalog = (ListView) view.findViewById(R.id.lv_catalog); - mLvCatalog.setOnItemClickListener(mCatalogOnItemClick); - mLvTag = (ListView) view.findViewById(R.id.lv_tag); - mLvTag.setOnItemClickListener(mTagOnItemClick); - if (mCatalogAdapter == null) { - mCatalogAdapter = new SoftwareCatalogListAdapter(); - sendRequestCatalogData(mCatalogHandler); - } - mLvCatalog.setAdapter(mCatalogAdapter); - - if (mTagAdapter == null) { - mTagAdapter = new SoftwareCatalogListAdapter(); - } - mLvTag.setAdapter(mTagAdapter); - - if (mSoftwareAdapter == null) { - mSoftwareAdapter = new SoftwareAdapter(); - } - - mLvSoftware = (ListView) view.findViewById(R.id.lv_software); - mLvSoftware.setOnItemClickListener(this); - mLvSoftware.setOnScrollListener(this); - mLvSoftware.setAdapter(mSoftwareAdapter); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - SoftwareDec software = (SoftwareDec) mSoftwareAdapter.getItem(position); - if (software != null) - UIHelper.showUrlRedirect(view.getContext(), software.getUrl()); - } - - @Override - public boolean onBackPressed() { - mEmptyView.setErrorType(EmptyLayout.HIDE_LAYOUT); - mCurrentPage = 0; - switch (curScreen) { - case SCREEN_SOFTWARE: - curScreen = SCREEN_TAG; - mScrollLayout.scrollToScreen(SCREEN_TAG); - return true; - case SCREEN_TAG: - curScreen = SCREEN_CATALOG; - mScrollLayout.scrollToScreen(SCREEN_CATALOG); - return true; - case SCREEN_CATALOG: - return false; - } - return super.onBackPressed(); - } - - private void sendRequestCatalogData(AsyncHttpResponseHandler handler) { - mState = STATE_REFRESH; - mEmptyView.setErrorType(EmptyLayout.NETWORK_LOADING); - OSChinaApi.getSoftwareCatalogList(mCurrentTag, handler); - } - - private void sendRequestTagData() { - OSChinaApi.getSoftwareTagList(mCurrentTag, mCurrentPage, - mSoftwareHandler); - } - - private void executeOnLoadDataSuccess(List data) { - if (data == null) { - return; - } - mEmptyView.setErrorType(EmptyLayout.HIDE_LAYOUT); - - if (mCurrentPage == 0) { - mSoftwareAdapter.clear(); - } - - for (int i = 0; i < data.size(); i++) { - if (compareTo(mSoftwareAdapter.getData(), data.get(i))) { - data.remove(i); - } - } - int adapterState = ListBaseAdapter.STATE_EMPTY_ITEM; + OnItemClickListener, OnScrollListener { + protected static final int STATE_NONE = 0; + protected static final int STATE_REFRESH = 1; + protected static final int STATE_LOADMORE = 2; + + private final static int SCREEN_CATALOG = 0; + private final static int SCREEN_TAG = 1; + private final static int SCREEN_SOFTWARE = 2; + + private static ScrollLayout mScrollLayout; + private static ListView mLvCatalog, mLvTag, mLvSoftware; + private static EmptyLayout mEmptyView; + private static SoftwareCatalogListAdapter mCatalogAdapter, mTagAdapter; + private static SoftwareAdapter mSoftwareAdapter; + private static int mState = STATE_NONE; + private static int curScreen = SCREEN_CATALOG;// 默认当前屏幕 + private static int mCurrentTag; + private static int mCurrentPage; + + private AsyncHttpResponseHandler mCatalogHandler = new AsyncHttpResponseHandler() { + + @Override + public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { + try { + SoftwareCatalogList list = XmlUtils.toBean( + SoftwareCatalogList.class, new ByteArrayInputStream( + arg2)); + if (mState == STATE_REFRESH) + mCatalogAdapter.clear(); + List data = list.getSoftwarecataloglist(); + mCatalogAdapter.addData(data); + mEmptyView.setErrorType(EmptyLayout.HIDE_LAYOUT); + if (data.size() == 0 && mState == STATE_REFRESH) { + mEmptyView.setErrorType(EmptyLayout.NODATA); + } else { + mCatalogAdapter + .setState(ListBaseAdapter.STATE_LESS_ONE_PAGE); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(arg0, arg1, arg2, e); + } + } + + @Override + public void onFailure(int arg0, Header[] arg1, byte[] arg2, + Throwable arg3) { + mEmptyView.setErrorType(EmptyLayout.NETWORK_ERROR); + } + + public void onFinish() { + mState = STATE_NONE; + } + }; + + private AsyncHttpResponseHandler mTagHandler = new AsyncHttpResponseHandler() { + + @Override + public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { + try { + SoftwareCatalogList list = XmlUtils.toBean( + SoftwareCatalogList.class, new ByteArrayInputStream( + arg2)); + if (mState == STATE_REFRESH) + mTagAdapter.clear(); + List data = list.getSoftwarecataloglist(); + mTagAdapter.addData(data); + mEmptyView.setErrorType(EmptyLayout.HIDE_LAYOUT); + if (data.size() == 0 && mState == STATE_REFRESH) { + mEmptyView.setErrorType(EmptyLayout.NODATA); + } else { + mTagAdapter.setState(ListBaseAdapter.STATE_LESS_ONE_PAGE); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(arg0, arg1, arg2, e); + } + } + + @Override + public void onFailure(int arg0, Header[] arg1, byte[] arg2, + Throwable arg3) { + mEmptyView.setErrorType(EmptyLayout.NETWORK_ERROR); + } + + public void onFinish() { + mState = STATE_NONE; + } + }; + + private AsyncHttpResponseHandler mSoftwareHandler = new AsyncHttpResponseHandler() { + + @Override + public void onSuccess(int statusCode, Header[] headers, + byte[] responseBytes) { + try { + SoftwareList list = XmlUtils.toBean(SoftwareList.class, + new ByteArrayInputStream(responseBytes)); + executeOnLoadDataSuccess(list.getSoftwarelist()); + + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseBytes, null); + } + } + + @Override + public void onFailure(int arg0, Header[] arg1, byte[] arg2, + Throwable arg3) { + mEmptyView.setErrorType(EmptyLayout.NETWORK_ERROR); + } + + public void onFinish() { + mState = STATE_NONE; + } + }; + + private OnItemClickListener mCatalogOnItemClick = new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView parent, View view, int position, + long id) { + SoftwareType type = (SoftwareType) mCatalogAdapter + .getItem(position); + if (type != null && type.getTag() > 0) { + // 加载二级分类 + curScreen = SCREEN_TAG; + mScrollLayout.scrollToScreen(curScreen); + mCurrentTag = type.getTag(); + sendRequestCatalogData(mTagHandler); + } + } + }; + + private OnItemClickListener mTagOnItemClick = new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView parent, View view, int position, + long id) { + SoftwareType type = (SoftwareType) mTagAdapter.getItem(position); + if (type != null && type.getTag() > 0) { + // 加载二级分类里面的软件列表 + curScreen = SCREEN_SOFTWARE; + mScrollLayout.scrollToScreen(curScreen); + mCurrentTag = type.getTag(); + mState = STATE_REFRESH; + sendRequestTagData(); + } + } + }; + + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_opensoftware, container, + false); + initViews(view); + return view; + } + + private void initViews(View view) { + mScrollLayout = (ScrollLayout) view.findViewById(R.id.scrolllayout); + mScrollLayout.setIsScroll(false); + + mEmptyView = (EmptyLayout) view.findViewById(R.id.error_layout); + mLvCatalog = (ListView) view.findViewById(R.id.lv_catalog); + mLvCatalog.setOnItemClickListener(mCatalogOnItemClick); + mLvTag = (ListView) view.findViewById(R.id.lv_tag); + mLvTag.setOnItemClickListener(mTagOnItemClick); + if (mCatalogAdapter == null) { + mCatalogAdapter = new SoftwareCatalogListAdapter(); + sendRequestCatalogData(mCatalogHandler); + } + mLvCatalog.setAdapter(mCatalogAdapter); + + if (mTagAdapter == null) { + mTagAdapter = new SoftwareCatalogListAdapter(); + } + mLvTag.setAdapter(mTagAdapter); + + if (mSoftwareAdapter == null) { + mSoftwareAdapter = new SoftwareAdapter(); + } + + mLvSoftware = (ListView) view.findViewById(R.id.lv_software); + mLvSoftware.setOnItemClickListener(this); + mLvSoftware.setOnScrollListener(this); + mLvSoftware.setAdapter(mSoftwareAdapter); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, + long id) { + SoftwareDec software = (SoftwareDec) mSoftwareAdapter.getItem(position); + if (software != null) + UIHelper.showUrlRedirect(view.getContext(), software.getUrl()); + } + + @Override + public boolean onBackPressed() { + mEmptyView.setErrorType(EmptyLayout.HIDE_LAYOUT); + mCurrentPage = 0; + switch (curScreen) { + case SCREEN_SOFTWARE: + curScreen = SCREEN_TAG; + mScrollLayout.scrollToScreen(SCREEN_TAG); + return true; + case SCREEN_TAG: + curScreen = SCREEN_CATALOG; + mScrollLayout.scrollToScreen(SCREEN_CATALOG); + return true; + case SCREEN_CATALOG: + return false; + } + return super.onBackPressed(); + } + + private void sendRequestCatalogData(AsyncHttpResponseHandler handler) { + mState = STATE_REFRESH; + mEmptyView.setErrorType(EmptyLayout.NETWORK_LOADING); + OSChinaApi.getSoftwareCatalogList(mCurrentTag, handler); + } + + private void sendRequestTagData() { + OSChinaApi.getSoftwareTagList(mCurrentTag, mCurrentPage, + mSoftwareHandler); + } + + private void executeOnLoadDataSuccess(List data) { + if (data == null) { + return; + } + mEmptyView.setErrorType(EmptyLayout.HIDE_LAYOUT); + + if (mCurrentPage == 0) { + mSoftwareAdapter.clear(); + } + + for (int i = 0; i < data.size(); i++) { + if (compareTo(mSoftwareAdapter.getData(), data.get(i))) { + data.remove(i); + } + } + int adapterState = ListBaseAdapter.STATE_EMPTY_ITEM; if (mSoftwareAdapter.getCount() == 0 && mState == STATE_NONE) { - mEmptyView.setErrorType(EmptyLayout.NODATA); + mEmptyView.setErrorType(EmptyLayout.NODATA); } else if (data.size() == 0 || (data.size() < 20 && mCurrentPage == 0)) { - adapterState = ListBaseAdapter.STATE_NO_MORE; + adapterState = ListBaseAdapter.STATE_NO_MORE; } else { - adapterState = ListBaseAdapter.STATE_LOAD_MORE; + adapterState = ListBaseAdapter.STATE_LOAD_MORE; } mSoftwareAdapter.setState(adapterState); mSoftwareAdapter.addData(data); - } - - private boolean compareTo(List data, SoftwareDec enity) { - int s = data.size(); - if (enity != null) { - for (int i = 0; i < s; i++) { - if (enity.getName().equals( - ((SoftwareDec) data.get(i)).getName())) { - return true; - } - } - } - return false; - } - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { - // 数据已经全部加载,或数据为空时,或正在加载,不处理滚动事件 - if (mState == STATE_NOMORE || mState == STATE_LOADMORE - || mState == STATE_REFRESH) { - return; - } - if (mSoftwareAdapter != null - && mSoftwareAdapter.getDataSize() > 0 - && mLvSoftware.getLastVisiblePosition() == (mLvSoftware - .getCount() - 1)) { - if (mState == STATE_NONE - && mSoftwareAdapter.getState() == ListBaseAdapter.STATE_LOAD_MORE) { - mState = STATE_LOADMORE; - mCurrentPage++; - sendRequestTagData(); - } - } - } + } + + private boolean compareTo(List data, SoftwareDec enity) { + int s = data.size(); + if (enity != null) { + for (int i = 0; i < s; i++) { + if (enity.getName().equals( + ((SoftwareDec) data.get(i)).getName())) { + return true; + } + } + } + return false; + } + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, + int visibleItemCount, int totalItemCount) { + // 数据已经全部加载,或数据为空时,或正在加载,不处理滚动事件 + if (mState == STATE_NOMORE || mState == STATE_LOADMORE + || mState == STATE_REFRESH) { + return; + } + if (mSoftwareAdapter != null + && mSoftwareAdapter.getDataSize() > 0 + && mLvSoftware.getLastVisiblePosition() == (mLvSoftware + .getCount() - 1)) { + if (mState == STATE_NONE + && mSoftwareAdapter.getState() == ListBaseAdapter.STATE_LOAD_MORE) { + mState = STATE_LOADMORE; + mCurrentPage++; + sendRequestTagData(); + } + } + } } diff --git a/app/src/main/java/net/oschina/app/fragment/SoftwareDetailFragment.java b/app/src/main/java/net/oschina/app/fragment/SoftwareDetailFragment.java deleted file mode 100644 index 6e76225e382bf07d115cb312b37a4dfa24fc2388..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/SoftwareDetailFragment.java +++ /dev/null @@ -1,196 +0,0 @@ -package net.oschina.app.fragment; - -import android.text.Editable; -import android.text.TextUtils; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.CommonDetailFragment; -import net.oschina.app.bean.FavoriteList; -import net.oschina.app.bean.Software; -import net.oschina.app.bean.SoftwareDetail; -import net.oschina.app.bean.Tweet; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.ThemeSwitchUtils; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.URLsUtils; -import net.oschina.app.util.XmlUtils; - -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; - -/** - * Created by 火蚁 on 15/5/26. - */ -public class SoftwareDetailFragment extends CommonDetailFragment { - - private String mIden; - - @Override - protected String getCacheKey() { - if (TextUtils.isEmpty(mIden)) { - return "software_" + mId; - } - return "software_" + mIden; - } - - @Override - protected void sendRequestDataForNet() { - // 通过id来获取软件详情 - if (mId > 0) { - OSChinaApi.getSoftwareDetail(mId, mDetailHeandler); - return; - } - - if (TextUtils.isEmpty(mIden)) { - executeOnLoadDataError(); - return; - } - OSChinaApi.getSoftwareDetail(mIden, mDetailHeandler); - } - - @Override - public void initData() { - super.initData(); - mIden = getActivity().getIntent().getStringExtra("ident"); - if (TextUtils.isEmpty(mIden)) { - return; - } - try { - mIden = URLEncoder.encode(mIden, "UTF-8"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - } - - @Override - protected Software parseData(InputStream is) { - return XmlUtils.toBean(SoftwareDetail.class, is).getSoftware(); - } - - @Override - protected String getWebViewBody(Software detail) { - mId = detail.getId(); - if (TextUtils.isEmpty(detail.getBody())) { - return ""; - } - StringBuffer body = new StringBuffer(); - body.append(ThemeSwitchUtils.getWebViewBodyString()); - body.append(UIHelper.WEB_STYLE).append(UIHelper.WEB_LOAD_IMAGES); - // 添加title - String title = ""; - // 判断是否推荐 - if (mDetail.getRecommended() == 4) { - title = String.format("
    %s %s
    ", mDetail.getLogo(), mDetail.getExtensionTitle(), mDetail.getTitle(), "file:///android_asset/ic_soft_recommend.png"); - } else { - title = String.format("
    %s %s
    ", mDetail.getLogo(), mDetail.getExtensionTitle(), mDetail.getTitle()); - } - body.append(title); - // 添加图片点击放大支持 - body.append(UIHelper.setHtmlCotentSupportImagePreview(mDetail.getBody())); - - // 软件信息 - body.append("
    "); - if (!TextUtils.isEmpty(mDetail.getAuthor())) { - String author = String.format("%s", mDetail.getAuthorId(), mDetail.getAuthor()); - body.append(String.format("
  • 软件作者:  %s
  • ", author)); - } - body.append(String.format("
  • 开源协议:  %s
  • ", mDetail.getLicense())); - body.append(String.format("
  • 开发语言:  %s
  • ", mDetail.getLanguage())); - body.append(String.format("
  • 操作系统:  %s
  • ", mDetail.getOs())); - body.append(String.format("
  • 收录时间:  %s
  • ", mDetail.getRecordtime())); - body.append("
    "); - - // 软件的首页、文档、下载 - body.append("
    "); - if (!TextUtils.isEmpty(mDetail.getHomepage())) { - body.append(String.format("
  • 软件首页
  • ", mDetail.getHomepage())); - } - if (!TextUtils.isEmpty(mDetail.getDocument())) { - body.append(String.format("
  • 软件文档
  • ", mDetail.getDocument())); - } - if (!TextUtils.isEmpty(mDetail.getDownload())) { - body.append(String.format("
  • 软件下载
  • ", mDetail.getDownload())); - } - body.append("
    "); - // 封尾 - body.append(""); - return body.toString(); - } - - @Override - protected void showCommentView() { - if (mDetail != null) - UIHelper.showSoftWareTweets(getActivity(), mDetail.getId()); - } - - @Override - public void onClickSendButton(Editable str) { - if (mDetail.getId() == 0) { - AppContext.showToast("无法获取该软件~"); - return; - } - if (!TDevice.hasInternet()) { - AppContext.showToastShort(R.string.tip_network_error); - return; - } - if (!AppContext.getInstance().isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - if (TextUtils.isEmpty(str)) { - AppContext.showToastShort(R.string.tip_comment_content_empty); - return; - } - Tweet tweet = new Tweet(); - tweet.setAuthorid(AppContext.getInstance().getLoginUid()); - tweet.setBody(str.toString()); - showWaitDialog(R.string.progress_submit); - OSChinaApi.pubSoftWareTweet(tweet, mDetail.getId(), mCommentHandler); - } - - @Override - protected int getCommentType() { - return 0; - } - - @Override - protected int getFavoriteTargetType() { - return FavoriteList.TYPE_SOFTWARE; - } - - @Override - protected int getFavoriteState() { - return mDetail.getFavorite(); - } - - @Override - protected void updateFavoriteChanged(int newFavoritedState) { - mDetail.setFavorite(newFavoritedState); - saveCache(mDetail); - } - - @Override - protected int getCommentCount() { - return mDetail.getTweetCount(); - } - - @Override - protected String getShareTitle() { - return String.format("%s %s", mDetail.getExtensionTitle(), mDetail.getTitle()); - } - - @Override - protected String getShareContent() { - return StringUtils.getSubString(0, 55, - getFilterHtmlBody(mDetail.getBody())); - } - - @Override - protected String getShareUrl() { - return String.format(URLsUtils.URL_MOBILE + "p/%s", mIden); - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/SoftwareListFragment.java b/app/src/main/java/net/oschina/app/fragment/SoftwareListFragment.java index 266682c0aa08b12e168aec2ede8d086b8a6fd2a0..3d28bb1937cd39431ea81b8a7356a7d71cff6523 100644 --- a/app/src/main/java/net/oschina/app/fragment/SoftwareListFragment.java +++ b/app/src/main/java/net/oschina/app/fragment/SoftwareListFragment.java @@ -1,8 +1,8 @@ package net.oschina.app.fragment; -import java.io.InputStream; -import java.io.Serializable; -import java.util.List; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; import net.oschina.app.adapter.SoftwareAdapter; import net.oschina.app.api.remote.OSChinaApi; @@ -11,82 +11,83 @@ import net.oschina.app.bean.Entity; import net.oschina.app.bean.ListEntity; import net.oschina.app.bean.SoftwareDec; import net.oschina.app.bean.SoftwareList; -import net.oschina.app.util.UIHelper; import net.oschina.app.util.XmlUtils; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; + +import java.io.InputStream; +import java.io.Serializable; +import java.util.List; public class SoftwareListFragment extends BaseListFragment { public static final String BUNDLE_SOFTWARE = "BUNDLE_SOFTWARE"; - protected static final String TAG = SoftwareListFragment.class - .getSimpleName(); private static final String CACHE_KEY_PREFIX = "softwarelist_"; private String softwareType = "recommend"; + @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Bundle args = getArguments(); - if (args != null) { - softwareType = args.getString(BUNDLE_SOFTWARE); - } + super.onCreate(savedInstanceState); + Bundle args = getArguments(); + if (args != null) { + softwareType = args.getString(BUNDLE_SOFTWARE); + } } @Override protected SoftwareAdapter getListAdapter() { - return new SoftwareAdapter(); + return new SoftwareAdapter(); } @Override protected String getCacheKeyPrefix() { - return CACHE_KEY_PREFIX + softwareType; + return CACHE_KEY_PREFIX + softwareType; } @Override protected SoftwareList parseList(InputStream is) throws Exception { - SoftwareList list = XmlUtils.toBean(SoftwareList.class, is); - return list; + SoftwareList list = XmlUtils.toBean(SoftwareList.class, is); + return list; } @Override protected ListEntity readList(Serializable seri) { - return ((SoftwareList) seri); + return ((SoftwareList) seri); } @Override protected void sendRequestData() { - OSChinaApi.getSoftwareList(softwareType, mCurrentPage, mHandler); + OSChinaApi.getSoftwareList(softwareType, mCurrentPage, mHandler); } @Override public void onItemClick(AdapterView parent, View view, int position, - long id) { - SoftwareDec softwaredec = (SoftwareDec) mAdapter.getItem(position); - if (softwaredec != null) { - String ident = softwaredec.getUrl().substring(softwaredec.getUrl().lastIndexOf("/") + 1); - UIHelper.showSoftwareDetail(getActivity(), ident); - // 放入已读列表 - saveToReadedList(view, SoftwareList.PREF_READED_SOFTWARE_LIST, - softwaredec.getName()); - } + long id) { + SoftwareDec softwaredec = mAdapter.getItem(position); + if (softwaredec != null) { + // String ident = softwaredec.getUrl().substring(softwaredec.getUrl().lastIndexOf("/") + 1); + int softwareDecId = softwaredec.getId(); + //SoftwareDetailActivity.show(getActivity(), ident); + net.oschina.app.improve.detail.general.SoftwareDetailActivity.show(getActivity(), softwareDecId); + // 放入已读列表 + saveToReadedList(view, SoftwareList.PREF_READED_SOFTWARE_LIST, + softwaredec.getName()); + } } @Override protected boolean compareTo(List data, Entity enity) { - int s = data.size(); - if (enity != null) { - for (int i = 0; i < s; i++) { - if (((SoftwareDec) enity).getName().equals( - ((SoftwareDec) data.get(i)).getName())) { - return true; - } - } - } - return false; + int s = data.size(); + if (enity != null) { + for (int i = 0; i < s; i++) { + if (((SoftwareDec) enity).getName().equals( + ((SoftwareDec) data.get(i)).getName())) { + return true; + } + } + } + return false; } } diff --git a/app/src/main/java/net/oschina/app/fragment/TweetDetailFragment.java b/app/src/main/java/net/oschina/app/fragment/TweetDetailFragment.java deleted file mode 100644 index b200143e8194e0b0554c828fe7cc9a90491d0190..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/TweetDetailFragment.java +++ /dev/null @@ -1,548 +0,0 @@ -package net.oschina.app.fragment; - -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.drawable.AnimationDrawable; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.webkit.WebView; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.AdapterView.OnItemLongClickListener; -import android.widget.ImageView; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.loopj.android.http.AsyncHttpResponseHandler; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.adapter.CommentAdapter; -import net.oschina.app.api.OperationResponseHandler; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BeseHaveHeaderListFragment; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.Comment; -import net.oschina.app.bean.CommentList; -import net.oschina.app.bean.Result; -import net.oschina.app.bean.ResultBean; -import net.oschina.app.bean.Tweet; -import net.oschina.app.bean.TweetDetail; -import net.oschina.app.cache.CacheManager; -import net.oschina.app.emoji.OnSendClickListener; -import net.oschina.app.ui.DetailActivity; -import net.oschina.app.ui.empty.EmptyLayout; -import net.oschina.app.util.DialogHelp; -import net.oschina.app.util.HTMLUtil; -import net.oschina.app.util.KJAnimations; -import net.oschina.app.util.PlatfromUtil; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.ThemeSwitchUtils; -import net.oschina.app.util.TypefaceUtils; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; -import net.oschina.app.widget.AvatarView; -import net.oschina.app.widget.RecordButtonUtil; -import net.oschina.app.widget.RecordButtonUtil.OnPlayListener; - -import cz.msebera.android.httpclient.Header; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.Serializable; -import java.util.List; - -/*** - * 动弹详情,实际每个item显示的数据类型是Comment - * - * TweetDetailFragment.java - * - * @author 火蚁(http://my.oschina.net/u/253900) - * - * @data 2015-1-28 上午11:48:41 - */ -public class TweetDetailFragment extends - BeseHaveHeaderListFragment implements - OnItemClickListener, OnItemLongClickListener, OnSendClickListener { - - private static final String CACHE_KEY_PREFIX = "tweet_"; - private static final String CACHE_KEY_TWEET_COMMENT = "tweet_comment_"; - private AvatarView mIvAvatar; - private TextView mTvName, mTvFrom, mTvTime, mTvCommentCount; - private WebView mContent; - private int mTweetId; - private Tweet mTweet; - private RelativeLayout mRlRecordSound; - private final RecordButtonUtil util = new RecordButtonUtil(); - - private TextView mLikeUser; - private TextView mTvLikeState; - - private DetailActivity outAty; - - @Override - protected CommentAdapter getListAdapter() { - return new CommentAdapter(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - outAty = (DetailActivity) getActivity(); - return super.onCreateView(inflater, container, savedInstanceState); - } - - @Override - protected String getCacheKeyPrefix() { - return CACHE_KEY_TWEET_COMMENT + mTweetId + "_" + mCurrentPage; - } - - @Override - protected CommentList parseList(InputStream is) throws Exception { - CommentList list = XmlUtils.toBean(CommentList.class, is); - return list; - } - - @Override - protected CommentList readList(Serializable seri) { - return ((CommentList) seri); - } - - @Override - protected void sendRequestData() { - OSChinaApi.getCommentList(mTweetId, CommentList.CATALOG_TWEET, - mCurrentPage, mHandler); - } - - /** - * 初始化声音动弹的录音View - * - * @param header - */ - private void initSoundView(View header) { - final ImageView playerButton = (ImageView) header - .findViewById(R.id.tweet_img_record); - final TextView playerTime = (TextView) header - .findViewById(R.id.tweet_tv_record); - final AnimationDrawable drawable = (AnimationDrawable) playerButton - .getBackground(); - mRlRecordSound = (RelativeLayout) header - .findViewById(R.id.tweet_bg_record); - mRlRecordSound.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (mTweet != null) { - util.startPlay(mTweet.getAttach(), playerTime); - } else { - AppContext.showToast("找不到语音动弹,可能已经被主人删除了"); - } - } - }); - - util.setOnPlayListener(new OnPlayListener() { - @SuppressWarnings("deprecation") - @Override - public void stopPlay() { - drawable.stop(); - playerButton.setBackgroundDrawable(drawable.getFrame(0)); - } - - @SuppressWarnings("deprecation") - @Override - public void starPlay() { - playerButton.setBackgroundDrawable(drawable); - drawable.start(); - } - }); - } - - @Override - public void onStop() { - super.onStop(); - if (util != null && util.isPlaying()) { - util.stopPlay(); - } - } - - @Override - protected boolean requestDataIfViewCreated() { - return false; - } - - @Override - public void onResume() { - super.onResume(); - outAty.emojiFragment.hideFlagButton(); - } - - private void fillUI() { - mIvAvatar.setAvatarUrl(mTweet.getPortrait()); - mIvAvatar.setUserInfo(mTweet.getAuthorid(), mTweet.getAuthor()); - mTvName.setText(mTweet.getAuthor()); - mTvTime.setText(StringUtils.friendly_time(mTweet.getPubDate())); - PlatfromUtil.setPlatFromString(mTvFrom, mTweet.getAppclient()); - - mTvCommentCount.setText(mTweet.getCommentCount() + ""); - if (StringUtils.isEmpty(mTweet.getAttach())) { - mRlRecordSound.setVisibility(View.GONE); - } else { - mRlRecordSound.setVisibility(View.VISIBLE); - } - fillWebViewBody(); - setLikeUser(); - setLikeState(); - } - - private void setLikeState() { - if (mTweet != null) { - if (mTweet.getIsLike() == 1) { - mTvLikeState.setTextColor(AppContext.getInstance().getResources().getColor(R.color.day_colorPrimary)); - } else { - mTvLikeState.setTextColor(AppContext.getInstance().getResources().getColor(R.color.gray)); - } - } - } - - private void setLikeUser() { - if (mTweet == null || mTweet.getLikeUser() == null - || mTweet.getLikeUser().isEmpty()) { - mLikeUser.setVisibility(View.GONE); - } else { - mLikeUser.setVisibility(View.VISIBLE); - mTweet.setLikeUsers(getActivity(), mLikeUser, false); - } - } - - /** - * 填充webview内容 - */ - private void fillWebViewBody() { - StringBuffer body = new StringBuffer(); - body.append(ThemeSwitchUtils.getWebViewBodyString()); - body.append(UIHelper.WEB_STYLE + UIHelper.WEB_LOAD_IMAGES); - - StringBuilder tweetbody = new StringBuilder(mTweet.getBody()); - - String tweetBody = TextUtils.isEmpty(mTweet.getImgSmall()) ? tweetbody - .toString() : tweetbody.toString() + "
    "; - body.append(setHtmlCotentSupportImagePreview(tweetBody)); - - UIHelper.addWebImageShow(getActivity(), mContent); - // 封尾 - body.append(""); - mContent.loadDataWithBaseURL(null, body.toString(), "text/html", - "utf-8", null); - } - - /** - * 添加图片放大支持 - * - * @param body - * @return - */ - private String setHtmlCotentSupportImagePreview(String body) { - // 过滤掉 img标签的width,height属性 - body = body.replaceAll("(]*?)\\s+width\\s*=\\s*\\S+", "$1"); - body = body.replaceAll("(]*?)\\s+height\\s*=\\s*\\S+", "$1"); - return body.replaceAll("(]+src=\")(\\S+)\"", - "$1$2\" onClick=\"javascript:mWebViewImageListener.showImagePreview('" - + mTweet.getImgBig() + "')\""); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - final Comment comment = mAdapter.getItem(position - 1); - if (comment == null) - return; - outAty.emojiFragment.getEditText().setHint("回复:" + comment.getAuthor()); - outAty.emojiFragment.getEditText().setTag(comment); - outAty.emojiFragment.showSoftKeyboard(); - } - - private final AsyncHttpResponseHandler mCommentHandler = new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - try { - ResultBean rsb = XmlUtils.toBean(ResultBean.class, - new ByteArrayInputStream(arg2)); - Result res = rsb.getResult(); - if (res.OK()) { - hideWaitDialog(); - AppContext.showToastShort(R.string.comment_publish_success); - mAdapter.setState(ListBaseAdapter.STATE_NO_MORE); - mAdapter.addItem(0, rsb.getComment()); - setTweetCommentCount(); - } else { - hideWaitDialog(); - AppContext.showToastShort(res.getErrorMessage()); - } - outAty.emojiFragment.clean(); - } catch (Exception e) { - e.printStackTrace(); - onFailure(arg0, arg1, arg2, e); - } - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) { - hideWaitDialog(); - AppContext.showToastShort(R.string.comment_publish_faile); - } - }; - - class DeleteOperationResponseHandler extends OperationResponseHandler { - - DeleteOperationResponseHandler(Object... args) { - super(args); - } - - @Override - public void onSuccess(int code, ByteArrayInputStream is, Object[] args) { - try { - Result res = XmlUtils.toBean(ResultBean.class, is).getResult(); - if (res.OK()) { - AppContext.showToastShort(R.string.delete_success); - mAdapter.removeItem(args[0]); - setTweetCommentCount(); - } else { - AppContext.showToastShort(res.getErrorMessage()); - } - } catch (Exception e) { - e.printStackTrace(); - onFailure(code, e.getMessage(), args); - } - } - - @Override - public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { - - } - - @Override - public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { - AppContext.showToastShort(R.string.delete_faile); - } - } - - private void handleDeleteComment(Comment comment) { - if (!AppContext.getInstance().isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - AppContext.showToastShort(R.string.deleting); - OSChinaApi.deleteComment(mTweetId, CommentList.CATALOG_TWEET, - comment.getId(), comment.getAuthorId(), - new DeleteOperationResponseHandler(comment)); - } - - private void setTweetCommentCount() { - mAdapter.notifyDataSetChanged(); - if (mTweet != null) { - mTweet.setCommentCount(mAdapter.getDataSize() + ""); - mTvCommentCount.setText(mTweet.getCommentCount() + ""); - } - } - - @Override - public boolean onItemLongClick(AdapterView parent, View view, - int position, long id) { - if (position - 1 == -1) { - return false; - } - final Comment item = mAdapter.getItem(position - 1); - if (item == null) - return false; - int itemsLen = item.getAuthorId() == AppContext.getInstance() - .getLoginUid() ? 2 : 1; - String[] items = new String[itemsLen]; - items[0] = getResources().getString(R.string.copy); - if (itemsLen == 2) { - items[1] = getResources().getString(R.string.delete); - } - DialogHelp.getSelectDialog(getActivity(), items, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (i == 0) { - TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(item - .getContent())); - } else if (i == 1) { - handleDeleteComment(item); - } - } - }).show(); - return true; - } - - @Override - protected void requestDetailData(boolean isRefresh) { - String key = getDetailCacheKey(); - mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); - if (TDevice.hasInternet() - && (!CacheManager.isExistDataCache(getActivity(), key) || isRefresh)) { - OSChinaApi.getTweetDetail(mTweetId, mDetailHandler); - } else { - readDetailCacheData(key); - } - } - - @Override - protected boolean isRefresh() { - return true; - } - - @Override - protected View initHeaderView() { - Intent args = getActivity().getIntent(); - mTweetId = args.getIntExtra("tweet_id", 0); - mTweet = (Tweet) args.getParcelableExtra("tweet"); - - mListView.setOnItemLongClickListener(this); - View header = LayoutInflater.from(getActivity()).inflate( - R.layout.list_header_tweet_detail, null); - mIvAvatar = (AvatarView) header.findViewById(R.id.iv_avatar); - - mTvName = (TextView) header.findViewById(R.id.tv_name); - mTvFrom = (TextView) header.findViewById(R.id.tv_from); - mTvTime = (TextView) header.findViewById(R.id.tv_time); - mTvCommentCount = (TextView) header.findViewById(R.id.tv_comment_count); - mContent = (WebView) header.findViewById(R.id.webview); - UIHelper.initWebView(mContent); - mContent.loadUrl("file:///android_asset/detail_page.html"); - initSoundView(header); - mLikeUser = (TextView) header.findViewById(R.id.tv_likeusers); - mTvLikeState = (TextView) header.findViewById(R.id.tv_like_state); - mTvLikeState.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - likeOption(); - } - }); - TypefaceUtils.setTypeface(mTvLikeState); - return header; - } - - private void likeOption() { - if (mTweet == null) - return; - AsyncHttpResponseHandler handler = new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) {} - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) {} - }; - if (AppContext.getInstance().isLogin()) { - if (mTweet.getIsLike() == 1) { - mTweet.setIsLike(0); - mTweet.getLikeUser().remove(0); - mTweet.setLikeCount(mTweet.getLikeCount() - 1); - OSChinaApi.pubUnLikeTweet(mTweetId, mTweet.getAuthorid(), - handler); - } else { - mTvLikeState.setAnimation(KJAnimations.getScaleAnimation(1.5f, - 300)); - mTweet.setIsLike(1); - mTweet.getLikeUser().add(0, - AppContext.getInstance().getLoginUser()); - mTweet.setLikeCount(mTweet.getLikeCount() + 1); - OSChinaApi - .pubLikeTweet(mTweetId, mTweet.getAuthorid(), handler); - } - setLikeState(); - mTweet.setLikeUsers(getActivity(), mLikeUser, false); - } else { - AppContext.showToast("先登陆再点赞~"); - UIHelper.showLoginActivity(getActivity()); - } - } - - @Override - protected String getDetailCacheKey() { - return CACHE_KEY_PREFIX + mTweetId; - } - - @Override - protected void executeOnLoadDetailSuccess(TweetDetail detailBean) { - mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); - this.mTweet = detailBean.getTweet(); - fillUI(); - mAdapter.setNoDataText(R.string.comment_empty); - } - - @Override - protected TweetDetail getDetailBean(ByteArrayInputStream is) { - return XmlUtils.toBean(TweetDetail.class, is); - } - - @Override - protected void executeOnLoadDataSuccess(List data) { - super.executeOnLoadDataSuccess(data); - int commentCount = StringUtils.toInt(mTweet == null ? 0 : this.mTweet - .getCommentCount()); - if (commentCount < (mAdapter.getCount() - 1)) { - commentCount = mAdapter.getCount() - 1; - } - mTvCommentCount.setText(commentCount + ""); - } - - @Override - public void onClickSendButton(Editable str) { - if (!AppContext.getInstance().isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - if (!TDevice.hasInternet()) { - AppContext.showToastShort(R.string.tip_network_error); - return; - } - if (TextUtils.isEmpty(str)) { - AppContext.showToastShort(R.string.tip_comment_content_empty); - return; - } - showWaitDialog(R.string.progress_submit); - try { - if (outAty.emojiFragment.getEditText().getTag() != null) { - Comment comment = (Comment) outAty.emojiFragment.getEditText() - .getTag(); - OSChinaApi.replyComment(mTweetId, CommentList.CATALOG_TWEET, - comment.getId(), comment.getAuthorId(), AppContext - .getInstance().getLoginUid(), str.toString(), - mCommentHandler); - } else { - OSChinaApi.publicComment(CommentList.CATALOG_TWEET, mTweetId, - AppContext.getInstance().getLoginUid(), str.toString(), - 0, mCommentHandler); - } - } catch (Exception e) { - } - } - - @Override - public void onClickFlagButton() {} - - @Override - public boolean onBackPressed() { - if (outAty.emojiFragment.isShowEmojiKeyBoard()) { - outAty.emojiFragment.hideAllKeyBoard(); - return true; - } - if (outAty.emojiFragment.getEditText().getTag() != null) { - outAty.emojiFragment.getEditText().setTag(null); - outAty.emojiFragment.getEditText().setHint("说点什么吧"); - return true; - } - return super.onBackPressed(); - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/TweetLikeUsersFragment.java b/app/src/main/java/net/oschina/app/fragment/TweetLikeUsersFragment.java deleted file mode 100644 index 8ece7df02377853f9d4cb2163d2743427c16ef5f..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/TweetLikeUsersFragment.java +++ /dev/null @@ -1,77 +0,0 @@ -package net.oschina.app.fragment; - -import java.io.InputStream; -import java.io.Serializable; - -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; - -import net.oschina.app.adapter.TweetLikeUsersAdapter; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseActivity; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.bean.TweetLikeUserList; -import net.oschina.app.bean.User; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; - -/** - * TweetLikeUsersFragment.java - * - * @author 火蚁(http://my.oschina.net/u/253900) - * - * @data 2015-3-26 下午4:04:12 - */ -public class TweetLikeUsersFragment extends BaseListFragment { - - @Override - protected TweetLikeUsersAdapter getListAdapter() { - // TODO Auto-generated method stub - return new TweetLikeUsersAdapter(); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - // TODO Auto-generated method stub - super.onCreate(savedInstanceState); - BaseActivity activity = (BaseActivity) getActivity(); - activity.setActionBarTitle("动弹点赞列表"); - } - - @Override - protected String getCacheKeyPrefix() { - // TODO Auto-generated method stub - return "tweet_like_list_" + mCatalog; - } - - @Override - protected TweetLikeUserList parseList(InputStream is) throws Exception { - // TODO Auto-generated method stub - TweetLikeUserList list = XmlUtils.toBean(TweetLikeUserList.class, is); - return list; - } - - @Override - protected TweetLikeUserList readList(Serializable seri) { - // TODO Auto-generated method stub - return (TweetLikeUserList) seri; - } - - @Override - protected void sendRequestData() { - // TODO Auto-generated method stub - OSChinaApi.getTweetLikeList(mCatalog, mCurrentPage, mHandler); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - // TODO Auto-generated method stub - User item = mAdapter.getItem(position); - if (item != null && item.getId() > 0) { - UIHelper.showUserCenter(getActivity(), item.getId(), item.getName()); - } - } -} - diff --git a/app/src/main/java/net/oschina/app/fragment/TweetPubFragment.java b/app/src/main/java/net/oschina/app/fragment/TweetPubFragment.java deleted file mode 100644 index 54a2d751c9dbd70f62af9eaf8bbfbe669e4a7b52..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/TweetPubFragment.java +++ /dev/null @@ -1,687 +0,0 @@ -package net.oschina.app.fragment; - -import android.app.Activity; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.Message; -import android.provider.MediaStore; -import android.provider.MediaStore.Images; -import android.support.annotation.Nullable; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.TextView; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.base.BaseFragment; -import net.oschina.app.bean.Tweet; -import net.oschina.app.emoji.EmojiKeyboardFragment; -import net.oschina.app.emoji.Emojicon; -import net.oschina.app.emoji.InputHelper; -import net.oschina.app.emoji.OnEmojiClickListener; -import net.oschina.app.service.ServerTaskUtils; -import net.oschina.app.ui.SelectFriendsActivity; -import net.oschina.app.util.DialogHelp; -import net.oschina.app.util.FileUtil; -import net.oschina.app.util.ImageUtils; -import net.oschina.app.util.SimpleTextWatcher; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.UIHelper; - -import org.kymjs.kjframe.Core; -import org.kymjs.kjframe.bitmap.BitmapCallBack; -import org.kymjs.kjframe.bitmap.BitmapCreate; -import org.kymjs.kjframe.bitmap.DiskImageRequest; -import org.kymjs.kjframe.http.KJAsyncTask; -import org.kymjs.kjframe.utils.FileUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; - -import butterknife.ButterKnife; -import butterknife.InjectView; - -public class TweetPubFragment extends BaseFragment implements - OnEmojiClickListener { - - public static final int ACTION_TYPE_ALBUM = 0; - public static final int ACTION_TYPE_PHOTO = 1; - public static final int ACTION_TYPE_RECORD = 2; // 录音 - public static final int ACTION_TYPE_TOPIC = 3; // 录音 - public static final String FROM_IMAGEPAGE_KEY = "from_image_page"; - - public static final String ACTION_TYPE = "action_type"; - - private static final int MAX_TEXT_LENGTH = 160; - private static final String TEXT_ATME = "@请输入用户名 "; - private static final String TEXT_SOFTWARE = "#请输入软件名#"; - - private static final int SELECT_FRIENDS_REEQUEST_CODE = 100; - - private String fromSharedTextContent = ""; - - @InjectView(R.id.ib_emoji_keyboard) - ImageButton mIbEmoji; - - @InjectView(R.id.ib_picture) - ImageButton mIbPicture; - - @InjectView(R.id.ib_mention) - ImageButton mIbMention; - - @InjectView(R.id.ib_trend_software) - ImageButton mIbTrendSoftware; - - @InjectView(R.id.tv_clear) - TextView mTvClear; - - @InjectView(R.id.rl_img) - View mLyImage; - - @InjectView(R.id.iv_img) - ImageView mIvImage; - - @InjectView(R.id.et_content) - EditText mEtInput; - - private MenuItem mSendMenu; - - private boolean mIsKeyboardVisible; - - private final EmojiKeyboardFragment keyboardFragment = new EmojiKeyboardFragment(); - - private String theLarge, theThumbnail; - private File imgFile; - - private final Handler handler = new Handler() { - @Override - public void handleMessage(Message msg) { - if (msg.what == 1 && msg.obj != null) { - // 显示图片 - mIvImage.setImageBitmap((Bitmap) msg.obj); - mLyImage.setVisibility(View.VISIBLE); - } - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - int mode = WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE - | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - getActivity().getWindow().setSoftInputMode(mode); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.pub_topic_menu, menu); - mSendMenu = menu.findItem(R.id.public_menu_send); - updateMenuState(); - } - - public void updateMenuState() { - if (mSendMenu == null) { - return; - } - if (mEtInput.getText().length() == 0) { - mSendMenu.setEnabled(false); - mSendMenu.setIcon(R.drawable.actionbar_unsend_icon); - } else { - mSendMenu.setEnabled(true); - mSendMenu.setIcon(R.drawable.actionbar_send_icon); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.public_menu_send: - handleSubmit(); - break; - default: - break; - } - return true; - } - - /** - * 方便外部Activity调用 - */ - public void setContentText(String content) { - fromSharedTextContent = content; - if (mEtInput != null) { - mEtInput.setText(content); - } - } - - /** - * 方便外部Activity调用 - */ - public void setContentImage(String url) { - handleImageFile(url); - } - - private void handleSubmit() { - if (!TDevice.hasInternet()) { - AppContext.showToastShort(R.string.tip_network_error); - return; - } - if (!AppContext.getInstance().isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - String content = mEtInput.getText().toString().trim(); - if (TextUtils.isEmpty(content)) { - mEtInput.requestFocus(); - AppContext.showToastShort(R.string.tip_content_empty); - return; - } - if (content.length() > MAX_TEXT_LENGTH) { - AppContext.showToastShort(R.string.tip_content_too_long); - return; - } - - Tweet tweet = new Tweet(); - tweet.setAuthorid(AppContext.getInstance().getLoginUid()); - tweet.setBody(content); - if (imgFile != null && imgFile.exists()) { - tweet.setImageFilePath(imgFile.getAbsolutePath()); - } - ServerTaskUtils.pubTweet(getActivity(), tweet); - if (mIsKeyboardVisible) { - TDevice.hideSoftKeyboard(getActivity().getCurrentFocus()); - } - getActivity().finish(); - } - - @Override - public View onCreateView(LayoutInflater inflater, - @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_tweet_pub, container, - false); - - initView(view); - return view; - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - Bundle bundle = getArguments(); - if (bundle != null) { - int action_type = bundle.getInt(ACTION_TYPE, -1); - goToSelectPicture(action_type); - final String imgUrl = bundle.getString(FROM_IMAGEPAGE_KEY); - handleImageUrl(imgUrl); - } - } - - /** - * 处理从第三方分享跳转来的图片 - * - * @param filePath - */ - private void handleImageFile(final String filePath) { - if (!StringUtils.isEmpty(filePath)) { - KJAsyncTask.execute(new Runnable() { - @Override - public void run() { - final Message msg = Message.obtain(); - msg.what = 1; - try { - msg.obj = BitmapCreate.bitmapFromStream( - new FileInputStream(filePath), 300, 300); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - String path = FileUtils.getSDCardPath() - + "/OSChina/tempfile.jpg"; - FileUtils.bitmapToFile((Bitmap) msg.obj, path); - imgFile = new File(path); - handler.sendMessage(msg); - } - }); - } - } - - /** - * 处理从图片浏览跳转来的图片 - * - * @param url - */ - private void handleImageUrl(final String url) { - if (!StringUtils.isEmpty(url)) { - final Message msg = Message.obtain(); - msg.what = 1; - byte[] cache = Core.getKJBitmap().getCache(url); - msg.obj = BitmapFactory.decodeByteArray(cache, 0, cache.length); - if (msg.obj == null) { - DiskImageRequest req = new DiskImageRequest(); - req.load(url, 300, 300, new BitmapCallBack() { - @Override - public void onSuccess(Bitmap bitmap) { - super.onSuccess(bitmap); - msg.obj = bitmap; - String path = FileUtils.getSDCardPath() - + "/OSChina/tempfile.jpg"; - handler.sendMessage(msg); - FileUtils.bitmapToFile((Bitmap) msg.obj, path); - imgFile = new File(path); - } - }); - } else { - String path = FileUtils.getSDCardPath() - + "/OSChina/tempfile.jpg"; - FileUtils.bitmapToFile((Bitmap) msg.obj, path); - imgFile = new File(path); - handler.sendMessage(msg); - } - } - } - - @Override - public void initView(View view) { - super.initView(view); - ButterKnife.inject(this, view); - setHasOptionsMenu(true); - mIbEmoji.setOnClickListener(this); - mIbPicture.setOnClickListener(this); - mIbMention.setOnClickListener(this); - mIbTrendSoftware.setOnClickListener(this); - mTvClear.setOnClickListener(this); - mTvClear.setText(String.valueOf(MAX_TEXT_LENGTH)); - view.findViewById(R.id.iv_clear_img).setOnClickListener(this); - - mEtInput.addTextChangedListener(new SimpleTextWatcher() { - @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { - mTvClear.setText((MAX_TEXT_LENGTH - s.length()) + ""); - updateMenuState(); - } - }); - // 获取保存的tweet草稿 - mEtInput.setText(AppContext.getTweetDraft()); - mEtInput.setSelection(mEtInput.getText().toString().length()); - - mEtInput.addTextChangedListener(new SimpleTextWatcher() { - @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { - mTvClear.setText((MAX_TEXT_LENGTH - s.length()) + ""); - } - }); - // 获取保存的tweet草稿 - String content = fromSharedTextContent; - if (StringUtils.isEmpty(fromSharedTextContent)) { - content = AppContext.getTweetDraft(); - } - mEtInput.setText(content); - mEtInput.setSelection(mEtInput.getText().toString().length()); - - getFragmentManager().beginTransaction() - .replace(R.id.emoji_keyboard_fragment, keyboardFragment) - .commit(); - keyboardFragment.setOnEmojiClickListener(new OnEmojiClickListener() { - @Override - public void onEmojiClick(Emojicon v) { - InputHelper.input2OSC(mEtInput, v); - } - - @Override - public void onDeleteButtonClick(View v) { - InputHelper.backspace(mEtInput); - } - }); - } - - @Override - public boolean onBackPressed() { - final String tweet = mEtInput.getText().toString(); - if (!TextUtils.isEmpty(tweet)) { - DialogHelp.getConfirmDialog(getActivity(), "是否保存为草稿?", new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - AppContext.setTweetDraft(tweet); - getActivity().finish(); - } - }, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - AppContext.setTweetDraft(""); - getActivity().finish(); - } - }).show(); - return true; - } - return super.onBackPressed(); - } - - @Override - public void onClick(View v) { - final int id = v.getId(); - if (id == R.id.ib_picture) { - handleSelectPicture(); - } else if (id == R.id.ib_mention) { - //insertMention(); - handleSelectFriends(); - } else if (id == R.id.ib_trend_software) { - insertTrendSoftware(); - } else if (id == R.id.tv_clear) { - handleClearWords(); - } else if (id == R.id.iv_clear_img) { - mIvImage.setImageBitmap(null); - mLyImage.setVisibility(View.GONE); - imgFile = null; - } else if (id == R.id.ib_emoji_keyboard) { - if (!keyboardFragment.isShow()) {// emoji隐藏中 - keyboardFragment.showEmojiKeyBoard(); - keyboardFragment.hideSoftKeyboard(); - } else { - keyboardFragment.hideEmojiKeyBoard(); - keyboardFragment.showSoftKeyboard(mEtInput); - } - } - } - - @Override - public void onActivityResult(final int requestCode, final int resultCode, - final Intent imageReturnIntent) { - if (resultCode != Activity.RESULT_OK) - return; - if(requestCode == SELECT_FRIENDS_REEQUEST_CODE) { - //选中好友的名字 - String names[] = imageReturnIntent.getStringArrayExtra("names"); - if(names != null && names.length > 0) { - //拼成字符串 - String text = ""; - for(String n : names) { - text += "@" + n + " "; - } - //插入到文本中 - mEtInput.getText().insert(mEtInput.getSelectionStart(), text); - } - return; - } - new Thread() { - private String selectedImagePath; - - @Override - public void run() { - Bitmap bitmap = null; - - if (requestCode == ImageUtils.REQUEST_CODE_GETIMAGE_BYSDCARD) { - if (imageReturnIntent == null) - return; - Uri selectedImageUri = imageReturnIntent.getData(); - if (selectedImageUri != null) { - selectedImagePath = ImageUtils.getImagePath( - selectedImageUri, getActivity()); - } - - if (selectedImagePath != null) { - theLarge = selectedImagePath; - } else { - bitmap = ImageUtils.loadPicasaImageFromGalley( - selectedImageUri, getActivity()); - } - - if (AppContext - .isMethodsCompat(android.os.Build.VERSION_CODES.ECLAIR_MR1)) { - String imaName = FileUtil.getFileName(theLarge); - if (imaName != null) - bitmap = ImageUtils.loadImgThumbnail(getActivity(), - imaName, - MediaStore.Images.Thumbnails.MICRO_KIND); - } - if (bitmap == null && !StringUtils.isEmpty(theLarge)) - bitmap = ImageUtils - .loadImgThumbnail(theLarge, 100, 100); - } else if (requestCode == ImageUtils.REQUEST_CODE_GETIMAGE_BYCAMERA) { - // 拍摄图片 - if (bitmap == null && !StringUtils.isEmpty(theLarge)) { - bitmap = ImageUtils - .loadImgThumbnail(theLarge, 100, 100); - } - } - - if (bitmap != null) {// 存放照片的文件夹 - String savePath = Environment.getExternalStorageDirectory() - .getAbsolutePath() + "/OSChina/Camera/"; - File savedir = new File(savePath); - if (!savedir.exists()) { - savedir.mkdirs(); - } - - String largeFileName = FileUtil.getFileName(theLarge); - String largeFilePath = savePath + largeFileName; - // 判断是否已存在缩略图 - if (largeFileName.startsWith("thumb_") - && new File(largeFilePath).exists()) { - theThumbnail = largeFilePath; - imgFile = new File(theThumbnail); - } else { - // 生成上传的800宽度图片 - String thumbFileName = "thumb_" + largeFileName; - theThumbnail = savePath + thumbFileName; - if (new File(theThumbnail).exists()) { - imgFile = new File(theThumbnail); - } else { - try { - // 压缩上传的图片 - ImageUtils.createImageThumbnail(getActivity(), - theLarge, theThumbnail, 800, 80); - imgFile = new File(theThumbnail); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - // 保存动弹临时图片 - // ((AppContext) getApplication()).setProperty( - // tempTweetImageKey, theThumbnail); - - Message msg = new Message(); - msg.what = 1; - msg.obj = bitmap; - handler.sendMessage(msg); - } - }; - }.start(); - } - - private void handleClearWords() { - if (TextUtils.isEmpty(mEtInput.getText().toString())) - return; - DialogHelp.getConfirmDialog(getActivity(), "是否清空内容?", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - mEtInput.getText().clear(); - if (mIsKeyboardVisible) { - TDevice.showSoftKeyboard(mEtInput); - } - } - }).show(); - } - - @Override - public void onResume() { - super.onResume(); - keyboardFragment.showSoftKeyboard(mEtInput); - keyboardFragment.hideEmojiKeyBoard(); - } - - /** 跳转选择好友*/ - private void handleSelectFriends() { - //如果没登录,则先去登录界面 - if (!AppContext.getInstance().isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - Intent intent = new Intent(getActivity(), SelectFriendsActivity.class); - startActivityForResult(intent, SELECT_FRIENDS_REEQUEST_CODE); - } - - private void handleSelectPicture() { - DialogHelp.getSelectDialog(getActivity(), getResources().getStringArray(R.array.choose_picture), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - goToSelectPicture(i); - } - }).show(); - } - - private void goToSelectPicture(int position) { - switch (position) { - case ACTION_TYPE_ALBUM: - Intent intent; - if (Build.VERSION.SDK_INT < 19) { - intent = new Intent(); - intent.setAction(Intent.ACTION_GET_CONTENT); - intent.setType("image/*"); - startActivityForResult(Intent.createChooser(intent, "选择图片"), - ImageUtils.REQUEST_CODE_GETIMAGE_BYSDCARD); - } else { - intent = new Intent(Intent.ACTION_PICK, - Images.Media.EXTERNAL_CONTENT_URI); - intent.setType("image/*"); - startActivityForResult(Intent.createChooser(intent, "选择图片"), - ImageUtils.REQUEST_CODE_GETIMAGE_BYSDCARD); - } - break; - case ACTION_TYPE_PHOTO: - // 判断是否挂载了SD卡 - String savePath = ""; - String storageState = Environment.getExternalStorageState(); - if (storageState.equals(Environment.MEDIA_MOUNTED)) { - savePath = Environment.getExternalStorageDirectory() - .getAbsolutePath() + "/oschina/Camera/"; - File savedir = new File(savePath); - if (!savedir.exists()) { - savedir.mkdirs(); - } - } - - // 没有挂载SD卡,无法保存文件 - if (StringUtils.isEmpty(savePath)) { - AppContext.showToastShort("无法保存照片,请检查SD卡是否挂载"); - return; - } - - String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss") - .format(new Date()); - String fileName = "osc_" + timeStamp + ".jpg";// 照片命名 - File out = new File(savePath, fileName); - Uri uri = Uri.fromFile(out); - - theLarge = savePath + fileName;// 该照片的绝对路径 - - intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); - startActivityForResult(intent, - ImageUtils.REQUEST_CODE_GETIMAGE_BYCAMERA); - break; - case ACTION_TYPE_TOPIC: - Bundle bundle = getArguments(); - if (bundle != null) { - String topic = bundle.getString("tweet_topic"); - setContentText(topic); - if (mEtInput != null) { - mEtInput.setSelection(topic.length()); - } - } - break; - default: - break; - } - } - - private void insertMention() { - TDevice.showSoftKeyboard(mEtInput); - // 在光标所在处插入“@用户名” - int curTextLength = mEtInput.getText().length(); - if (curTextLength >= MAX_TEXT_LENGTH) - return; - String atme = TEXT_ATME; - int start, end; - if ((MAX_TEXT_LENGTH - curTextLength) >= atme.length()) { - start = mEtInput.getSelectionStart() + 1; - end = start + atme.length() - 2; - } else { - int num = MAX_TEXT_LENGTH - curTextLength; - if (num < atme.length()) { - atme = atme.substring(0, num); - } - start = mEtInput.getSelectionStart() + 1; - end = start + atme.length() - 1; - } - if (start > MAX_TEXT_LENGTH || end > MAX_TEXT_LENGTH) { - start = MAX_TEXT_LENGTH; - end = MAX_TEXT_LENGTH; - } - mEtInput.getText().insert(mEtInput.getSelectionStart(), atme); - mEtInput.setSelection(start, end);// 设置选中文字 - } - - private void insertTrendSoftware() { - // 在光标所在处插入“#软件名#” - int curTextLength = mEtInput.getText().length(); - if (curTextLength >= MAX_TEXT_LENGTH) - return; - String software = TEXT_SOFTWARE; - int start, end; - if ((MAX_TEXT_LENGTH - curTextLength) >= software.length()) { - start = mEtInput.getSelectionStart() + 1; - end = start + software.length() - 2; - } else { - int num = MAX_TEXT_LENGTH - curTextLength; - if (num < software.length()) { - software = software.substring(0, num); - } - start = mEtInput.getSelectionStart() + 1; - end = start + software.length() - 1; - } - if (start > MAX_TEXT_LENGTH || end > MAX_TEXT_LENGTH) { - start = MAX_TEXT_LENGTH; - end = MAX_TEXT_LENGTH; - } - mEtInput.getText().insert(mEtInput.getSelectionStart(), software); - mEtInput.setSelection(start, end);// 设置选中文字 - } - - @Override - public void initData() {} - - @Override - public void onDeleteButtonClick(View v) {} - - @Override - public void onEmojiClick(Emojicon v) { - InputHelper.input2OSC(mEtInput, v); - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/TweetRecordFragment.java b/app/src/main/java/net/oschina/app/fragment/TweetRecordFragment.java deleted file mode 100644 index a7b3b5eae7969e299a433407e17ef8d41939a6e8..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/TweetRecordFragment.java +++ /dev/null @@ -1,213 +0,0 @@ -package net.oschina.app.fragment; - -import java.io.File; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.base.BaseFragment; -import net.oschina.app.bean.Tweet; -import net.oschina.app.service.ServerTaskUtils; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.UIHelper; -import net.oschina.app.widget.RecordButton; -import net.oschina.app.widget.RecordButton.OnFinishedRecordListener; -import net.oschina.app.widget.RecordButtonUtil.OnPlayListener; - -import org.kymjs.kjframe.utils.DensityUtils; - -import android.graphics.drawable.AnimationDrawable; -import android.os.Bundle; -import android.text.Editable; -import android.text.Selection; -import android.text.Spannable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.RelativeLayout; -import android.widget.RelativeLayout.LayoutParams; -import android.widget.TextView; -import butterknife.ButterKnife; -import butterknife.InjectView; - -/** - * 语音动弹发布界面 - * - * @author kymjs(kymjs123@gmail.com) - * - */ -public class TweetRecordFragment extends BaseFragment { - - @InjectView(R.id.tweet_layout_record) - RelativeLayout mLayout; - @InjectView(R.id.tweet_btn_record) - RecordButton mBtnRecort; - @InjectView(R.id.tweet_time_record) - TextView mTvTime; - @InjectView(R.id.tweet_text_record) - TextView mTvInputLen; - @InjectView(R.id.tweet_edit_record) - EditText mEtSpeech; - @InjectView(R.id.tweet_img_volume) - ImageView mImgVolume; - - public static int MAX_LEN = 160; - - private AnimationDrawable drawable; // 录音播放时的动画背景 - - private String strSpeech = "#语音动弹#"; - private int currentRecordTime = 0; - - @Override - public void onClick(View v) { - if (v == mLayout) { - mBtnRecort.playRecord(); - } - } - - @Override - public void initView(View view) { - RelativeLayout.LayoutParams params = (LayoutParams) mBtnRecort - .getLayoutParams(); - params.width = DensityUtils.getScreenW(getActivity()); - params.height = (int) (DensityUtils.getScreenH(getActivity()) * 0.4); - mBtnRecort.setLayoutParams(params); - mLayout.setOnClickListener(this); - - mBtnRecort.setOnFinishedRecordListener(new OnFinishedRecordListener() { - @Override - public void onFinishedRecord(String audioPath, int recordTime) { - currentRecordTime = recordTime; - mLayout.setVisibility(View.VISIBLE); - if (recordTime < 10) { - mTvTime.setText("0" + recordTime + "\""); - } else { - mTvTime.setText(recordTime + "\""); - } - } - - @Override - public void onCancleRecord() { - mLayout.setVisibility(View.GONE); - } - }); - - drawable = (AnimationDrawable) mImgVolume.getBackground(); - mBtnRecort.getAudioUtil().setOnPlayListener(new OnPlayListener() { - @Override - public void stopPlay() { - drawable.stop(); - mImgVolume.setBackgroundDrawable(drawable.getFrame(0)); - } - - @Override - public void starPlay() { - mImgVolume.setBackgroundDrawable(drawable); - drawable.start(); - } - }); - - mEtSpeech.addTextChangedListener(new TextWatcher() { - @Override - public void onTextChanged(CharSequence s, int start, int before, - int count) { - if (s.length() > MAX_LEN) { - mTvInputLen.setText("已达到最大长度"); - } else { - mTvInputLen.setText("您还可以输入" + (MAX_LEN - s.length()) - + "个字符"); - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, - int after) {} - - @Override - public void afterTextChanged(Editable s) { - if (s.length() > MAX_LEN) { - mEtSpeech.setText(s.subSequence(0, MAX_LEN)); - CharSequence text = mEtSpeech.getText(); - if (text instanceof Spannable) - Selection.setSelection((Spannable) text, MAX_LEN); - } - } - }); - } - - @Override - public void initData() {} - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); - View rootView = inflater.inflate(R.layout.item_tweet_pub_record, - container, false); - ButterKnife.inject(this, rootView); - initView(rootView); - initData(); - return rootView; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.pub_tweet_menu, menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.public_menu_send: - handleSubmit(mBtnRecort.getCurrentAudioPath()); - break; - } - return true; - } - - /** - * 发布动弹 - */ - private void handleSubmit(String audioPath) { - if (!TDevice.hasInternet()) { - AppContext.showToastShort(R.string.tip_network_error); - return; - } - if (!AppContext.getInstance().isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - if (StringUtils.isEmpty(audioPath)) { - AppContext.showToastShort(R.string.record_sound_notfound); - return; - } - File file = new File(audioPath); - if (!file.exists()) { - AppContext.showToastShort(R.string.record_sound_notfound); - return; - } - - String body = mEtSpeech.getText().toString(); - if (!StringUtils.isEmpty(body)) { - strSpeech = body; - } - Tweet tweet = new Tweet(); - tweet.setAuthorid(AppContext.getInstance().getLoginUid()); - tweet.setAudioPath(audioPath); - tweet.setBody(strSpeech); - ServerTaskUtils.pubTweet(getActivity(), tweet); - getActivity().finish(); - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/TweetsFragment.java b/app/src/main/java/net/oschina/app/fragment/TweetsFragment.java deleted file mode 100644 index d384666ca97597aedad4fb349a25b7a32d8013d1..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/TweetsFragment.java +++ /dev/null @@ -1,273 +0,0 @@ -package net.oschina.app.fragment; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemLongClickListener; -import cz.msebera.android.httpclient.Header; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.adapter.TweetAdapter; -import net.oschina.app.api.OperationResponseHandler; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.bean.Constants; -import net.oschina.app.bean.Result; -import net.oschina.app.bean.ResultBean; -import net.oschina.app.bean.Tweet; -import net.oschina.app.bean.TweetsList; -import net.oschina.app.interf.OnTabReselectListener; -import net.oschina.app.ui.empty.EmptyLayout; -import net.oschina.app.util.DialogHelp; -import net.oschina.app.util.HTMLUtil; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.Serializable; - -/** - * @author kymjs (http://www.kymjs.com) - */ -public class TweetsFragment extends BaseListFragment implements - OnItemLongClickListener, OnTabReselectListener { - - protected static final String TAG = TweetsFragment.class.getSimpleName(); - private static final String CACHE_KEY_PREFIX = "tweetslist_"; - - class DeleteTweetResponseHandler extends OperationResponseHandler { - - DeleteTweetResponseHandler(Object... args) { - super(args); - } - - @Override - public void onSuccess(int code, ByteArrayInputStream is, Object[] args) - throws Exception { - try { - Result res = XmlUtils.toBean(ResultBean.class, is).getResult(); - if (res != null && res.OK()) { - AppContext.showToastShort(R.string.delete_success); - Tweet tweet = (Tweet) args[0]; - mAdapter.removeItem(tweet); - mAdapter.notifyDataSetChanged(); - } else { - onFailure(code, res.getErrorMessage(), args); - } - } catch (Exception e) { - e.printStackTrace(); - onFailure(code, e.getMessage(), args); - } - } - - @Override - public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { - - } - - @Override - public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { - AppContext.showToastShort(R.string.delete_faile); - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (mCatalog > 0) { - IntentFilter filter = new IntentFilter( - Constants.INTENT_ACTION_USER_CHANGE); - filter.addAction(Constants.INTENT_ACTION_LOGOUT); - getActivity().registerReceiver(mReceiver, filter); - } - } - - @Override - public void onDestroy() { - if (mCatalog > 0) { - getActivity().unregisterReceiver(mReceiver); - } - super.onDestroy(); - } - - @Override - protected TweetAdapter getListAdapter() { - return new TweetAdapter(); - } - - @Override - protected String getCacheKeyPrefix() { - Bundle bundle = getArguments(); - if (bundle != null) { - String str = bundle.getString("topic"); - if (str != null) { - return str; - } - } - return CACHE_KEY_PREFIX + mCatalog; - } - - public String getTopic() { - Bundle bundle = getArguments(); - if (bundle != null) { - String str = bundle.getString("topic"); - if (str != null) { - return str; - } - } - return ""; - } - - @Override - protected TweetsList parseList(InputStream is) throws Exception { - TweetsList list = XmlUtils.toBean(TweetsList.class, is); - return list; - } - - @Override - protected TweetsList readList(Serializable seri) { - return ((TweetsList) seri); - } - - @Override - protected void sendRequestData() { - Bundle bundle = getArguments(); - if (bundle != null) { - String str = bundle.getString("topic"); - if (str != null) { - OSChinaApi.getTweetTopicList(mCurrentPage, str, mHandler); - return; - } - } - OSChinaApi.getTweetList(mCatalog, mCurrentPage, mHandler); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Tweet tweet = mAdapter.getItem(position); - if (tweet != null) { - UIHelper.showTweetDetail(view.getContext(), null, tweet.getId()); - } - } - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - setupContent(); - } - }; - - private void setupContent() { - if (AppContext.getInstance().isLogin()) { - mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); - requestData(true); - } else { - mCatalog = TweetsList.CATALOG_ME; - mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); - mErrorLayout.setErrorMessage(getString(R.string.unlogin_tip)); - } - } - - @Override - protected void requestData(boolean refresh) { - if (mCatalog > 0) { - if (AppContext.getInstance().isLogin()) { - mCatalog = AppContext.getInstance().getLoginUid(); - super.requestData(refresh); - } else { - mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); - mErrorLayout.setErrorMessage(getString(R.string.unlogin_tip)); - } - } else { - super.requestData(refresh); - } - } - - @Override - public void initView(View view) { - super.initView(view); - mListView.setOnItemLongClickListener(this); - mErrorLayout.setOnLayoutClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - if (mCatalog > 0) { - if (AppContext.getInstance().isLogin()) { - mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); - requestData(true); - } else { - UIHelper.showLoginActivity(getActivity()); - } - } else { - mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); - requestData(true); - } - } - }); - } - - @Override - public boolean onItemLongClick(AdapterView parent, View view, - int position, long id) { - Tweet tweet = mAdapter.getItem(position); - if (tweet != null) { - handleLongClick(tweet); - return true; - } - return false; - } - - private void handleLongClick(final Tweet tweet) { - String[] items = null; - if (AppContext.getInstance().getLoginUid() == tweet.getAuthorid()) { - items = new String[] { getResources().getString(R.string.copy), - getResources().getString(R.string.delete) }; - } else { - items = new String[] { getResources().getString(R.string.copy) }; - } - - DialogHelp.getSelectDialog(getActivity(), items, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (i == 0) { - TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(tweet.getBody())); - } else if (i == 1) { - handleDeleteTweet(tweet); - } - } - }).show(); - } - - private void handleDeleteTweet(final Tweet tweet) { - DialogHelp.getConfirmDialog(getActivity(), "是否删除该动弹?", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - OSChinaApi.deleteTweet(tweet.getAuthorid(), tweet - .getId(), new DeleteTweetResponseHandler(tweet)); - } - }).show(); - } - - @Override - public void onTabReselect() { - onRefresh(); - } - - @Override - protected long getAutoRefreshTime() { - // 最新动弹3分钟刷新一次 - if (mCatalog == TweetsList.CATALOG_LATEST) { - return 3 * 60; - } - return super.getAutoRefreshTime(); - } -} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/fragment/TweetsLikesFragment.java b/app/src/main/java/net/oschina/app/fragment/TweetsLikesFragment.java deleted file mode 100644 index 68f5c900fbe3c97294597815a7664c0eb80a9e14..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/TweetsLikesFragment.java +++ /dev/null @@ -1,183 +0,0 @@ -package net.oschina.app.fragment; - -import java.io.InputStream; -import java.io.Serializable; -import java.util.List; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.adapter.TweetLikeAdapter; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.bean.Constants; -import net.oschina.app.bean.Entity; -import net.oschina.app.bean.Notice; -import net.oschina.app.bean.Tweet; -import net.oschina.app.bean.TweetLike; -import net.oschina.app.bean.TweetLikeList; -import net.oschina.app.bean.TweetsList; -import net.oschina.app.service.NoticeUtils; -import net.oschina.app.ui.empty.EmptyLayout; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; -import net.oschina.app.viewpagerfragment.NoticeViewPagerFragment; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; - -/** - * 赞过我动弹的列表 - * - * @date 2014年10月10日 - */ -public class TweetsLikesFragment extends BaseListFragment { - - protected static final String TAG = TweetsLikesFragment.class - .getSimpleName(); - private static final String CACHE_KEY_PREFIX = "mytweets_like_list_"; - - private boolean mIsWatingLogin; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (mCatalog > 0) { - IntentFilter filter = new IntentFilter( - Constants.INTENT_ACTION_USER_CHANGE); - filter.addAction(Constants.INTENT_ACTION_LOGOUT); - getActivity().registerReceiver(mReceiver, filter); - } - } - - @Override - public void onDestroy() { - if (mCatalog > 0) { - getActivity().unregisterReceiver(mReceiver); - } - super.onDestroy(); - } - - @Override - protected TweetLikeAdapter getListAdapter() { - return new TweetLikeAdapter(); - } - - @Override - protected String getCacheKeyPrefix() { - return CACHE_KEY_PREFIX + mCatalog; - } - - @Override - protected TweetLikeList parseList(InputStream is) throws Exception { - TweetLikeList list = XmlUtils.toBean(TweetLikeList.class, is); - return list; - } - - @Override - protected TweetLikeList readList(Serializable seri) { - return ((TweetLikeList) seri); - } - - @Override - protected void sendRequestData() { - OSChinaApi.getTweetLikeList(mHandler); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Tweet tweet = mAdapter.getItem(position).getTweet(); - if (tweet != null) { - UIHelper.showTweetDetail(view.getContext(), null, tweet.getId()); - } - } - - private BroadcastReceiver mReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - setupContent(); - } - }; - - private void setupContent() { - if (AppContext.getInstance().isLogin()) { - mIsWatingLogin = false; - mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); - requestData(true); - } else { - mCatalog = TweetsList.CATALOG_ME; - mIsWatingLogin = true; - mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); - mErrorLayout.setErrorMessage(getString(R.string.unlogin_tip)); - } - } - - @Override - protected void requestData(boolean refresh) { - if (AppContext.getInstance().isLogin()) { - mCatalog = AppContext.getInstance().getLoginUid(); - mIsWatingLogin = false; - super.requestData(refresh); - } else { - mIsWatingLogin = true; - mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); - mErrorLayout.setErrorMessage(getString(R.string.unlogin_tip)); - } - } - - @Override - public void initView(View view) { - super.initView(view); - mErrorLayout.setOnLayoutClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - if (AppContext.getInstance().isLogin()) { - mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); - requestData(true); - } else { - UIHelper.showLoginActivity(getActivity()); - } - } - }); - } - - @Override - protected long getAutoRefreshTime() { - if (mCatalog == TweetsList.CATALOG_LATEST) { - return 3 * 60; - } - return super.getAutoRefreshTime(); - } - - @Override - protected void onRefreshNetworkSuccess() { - // TODO Auto-generated method stub - super.onRefreshNetworkSuccess(); - if (AppContext.getInstance().isLogin() - && mCatalog == AppContext.getInstance().getLoginUid() - && 4 == NoticeViewPagerFragment.sCurrentPage) { - NoticeUtils.clearNotice(Notice.TYPE_NEWLIKE); - UIHelper.sendBroadcastForNotice(getActivity()); - } - } - - protected boolean compareTo(List data, Entity enity) { - int s = data.size(); - - if (enity != null && enity instanceof TweetLike) { - TweetLike tweetLike = (TweetLike) enity; - for (int i = 0; i < s; i++) { - if (tweetLike.getUser().getId() == ((TweetLike)data.get(i)).getId()) { - return true; - } - } - } - return false; - } -} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/fragment/UserBlogFragment.java b/app/src/main/java/net/oschina/app/fragment/UserBlogFragment.java deleted file mode 100644 index b3ae9c3e96ddc74fa9680d2437b990ebaebf890a..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/UserBlogFragment.java +++ /dev/null @@ -1,61 +0,0 @@ -package net.oschina.app.fragment; - -import java.io.InputStream; -import java.io.Serializable; - -import net.oschina.app.adapter.BlogAdapter; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.bean.Blog; -import net.oschina.app.bean.BlogList; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; -import android.view.View; -import android.widget.AdapterView; - -/** - * 用户的博客列表(用用户的id来获取) - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年10月29日 下午5:09:13 - * - */ -public class UserBlogFragment extends BaseListFragment { - - protected static final String TAG = UserBlogFragment.class.getSimpleName(); - private static final String CACHE_KEY_PREFIX = "user_bloglist_"; - - @Override - protected BlogAdapter getListAdapter() { - return new BlogAdapter(); - } - - @Override - protected String getCacheKeyPrefix() { - return CACHE_KEY_PREFIX + mCatalog; - } - - @Override - protected BlogList parseList(InputStream is) throws Exception { - BlogList list = XmlUtils.toBean(BlogList.class, is); - return list; - } - - @Override - protected BlogList readList(Serializable seri) { - return ((BlogList) seri); - } - - @Override - protected void sendRequestData() { - OSChinaApi.getUserBlogList(mCatalog, "", mCatalog, mCurrentPage, mHandler); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Blog blog = (Blog) mAdapter.getItem(position); - if (blog != null) - UIHelper.showUrlRedirect(view.getContext(), blog.getUrl()); - } -} diff --git a/app/src/main/java/net/oschina/app/fragment/UserCenterFragment.java b/app/src/main/java/net/oschina/app/fragment/UserCenterFragment.java deleted file mode 100644 index 4acc36494451d8453a0ad9dd313eb732b3ffe5f3..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/UserCenterFragment.java +++ /dev/null @@ -1,473 +0,0 @@ -package net.oschina.app.fragment; - -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; - -import com.loopj.android.http.AsyncHttpResponseHandler; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.adapter.ActiveAdapter; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseFragment; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.Active; -import net.oschina.app.bean.Result; -import net.oschina.app.bean.ResultBean; -import net.oschina.app.bean.User; -import net.oschina.app.bean.UserInformation; -import net.oschina.app.ui.empty.EmptyLayout; -import net.oschina.app.util.DialogHelp; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.TDevice; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; -import net.oschina.app.widget.AvatarView; - - -import java.io.ByteArrayInputStream; -import java.util.List; - -import butterknife.ButterKnife; -import butterknife.InjectView; -import cz.msebera.android.httpclient.Header; - -/** - * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2014年10月29日 下午2:33:18 - * - */ - -public class UserCenterFragment extends BaseFragment implements - OnItemClickListener, OnScrollListener { - - private static final Object FEMALE = "女"; - - @InjectView(R.id.error_layout) - EmptyLayout mEmptyView; - - @InjectView(R.id.lv_user_active) - ListView mListView; - private ImageView mIvAvatar, mIvGender; - private TextView mTvName, mTvFollowing, mTvFollower, mTvSore, - mBtnPrivateMsg, mBtnFollowUser, mTvLastestLoginTime; - - private ActiveAdapter mAdapter; - private int mHisUid; - private String mHisName; - private int mUid; - private User mUser; - - private int mActivePage = 0; - - private final AsyncHttpResponseHandler mActiveHandler = new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - try { - UserInformation information = XmlUtils.toBean( - UserInformation.class, new ByteArrayInputStream(arg2)); - mUser = information.getUser(); - fillUI(); - List data = information.getActiveList(); - if (mState == STATE_REFRESH) - mAdapter.clear(); - mAdapter.addData(data); - mEmptyView.setErrorType(EmptyLayout.HIDE_LAYOUT); - if (data.size() == 0 && mState == STATE_REFRESH) { - mEmptyView.setErrorType(EmptyLayout.NODATA); - mAdapter.setState(ListBaseAdapter.STATE_NO_MORE); - } else if (data.size() == 0) { - if (mState == STATE_REFRESH) - mAdapter.setState(ListBaseAdapter.STATE_NO_MORE); - else - mAdapter.setState(ListBaseAdapter.STATE_NO_MORE); - } else { - mAdapter.setState(ListBaseAdapter.STATE_LOAD_MORE); - } - } catch (Exception e) { - onFailure(arg0, arg1, arg2, e); - e.printStackTrace(); - } - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) { - mEmptyView.setErrorType(EmptyLayout.NETWORK_ERROR); - } - - @Override - public void onFinish() { - mState = STATE_NONE; - } - }; - - @Override - public View onCreateView(LayoutInflater inflater, - @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); - View view = inflater.inflate(R.layout.fragment_user_center, container, - false); - - Bundle args = getArguments(); - - mHisUid = args.getInt("his_id", 0); - mHisName = args.getString("his_name"); - mUid = AppContext.getInstance().getLoginUid(); - ButterKnife.inject(this, view); - initView(view); - - return view; - } - - @Override - public void onClick(View v) { - final int id = v.getId(); - switch (id) { - case R.id.iv_avatar: - UIHelper.showUserAvatar(getActivity(), mUser.getPortrait()); - break; - case R.id.ly_following: - UIHelper.showFriends(getActivity(), mUser.getId(), 0); - break; - case R.id.ly_follower: - UIHelper.showFriends(getActivity(), mUser.getId(), 1); - break; - case R.id.tv_follow_user: - handleUserRelation(); - break; - case R.id.tv_private_message: - if (mHisUid == AppContext.getInstance().getLoginUid()) { - AppContext.showToast("不能给自己发送留言:)"); - return; - } - if (!AppContext.getInstance().isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - UIHelper.showMessageDetail(getActivity(), mHisUid, mHisName); - break; - case R.id.tv_blog: - UIHelper.showUserBlog(getActivity(), mHisUid); - break; - case R.id.tv_information: - showInformationDialog(); - break; - default: - break; - } - } - - @Override - public void initView(View view) { - mListView.setOnItemClickListener(this); - mListView.setOnScrollListener(this); - - View header = LayoutInflater.from(getActivity()).inflate( - R.layout.fragment_user_center_header, null, false); - - mIvAvatar = (ImageView) header.findViewById(R.id.iv_avatar); - mIvAvatar.setOnClickListener(this); - mIvGender = (ImageView) header.findViewById(R.id.iv_gender); - mTvName = (TextView) header.findViewById(R.id.tv_name); - mTvFollowing = (TextView) header.findViewById(R.id.tv_following_count); - header.findViewById(R.id.ly_following).setOnClickListener(this); - mTvFollower = (TextView) header.findViewById(R.id.tv_follower_count); - header.findViewById(R.id.ly_follower).setOnClickListener(this); - mTvSore = (TextView) header.findViewById(R.id.tv_score); - mTvLastestLoginTime = (TextView) header - .findViewById(R.id.tv_latest_login_time); - - mBtnPrivateMsg = (TextView) header - .findViewById(R.id.tv_private_message); - mBtnPrivateMsg.setOnClickListener(this); - mBtnFollowUser = (TextView) header.findViewById(R.id.tv_follow_user); - mBtnFollowUser.setOnClickListener(this); - - header.findViewById(R.id.tv_blog).setOnClickListener(this); - header.findViewById(R.id.tv_information).setOnClickListener(this); - - mListView.addHeaderView(header); - - mBtnPrivateMsg.setOnClickListener(this); - mBtnFollowUser.setOnClickListener(this); - - if (mAdapter == null) { - mAdapter = new ActiveAdapter(); - - fristSendGetUserInfomation(); - } - mListView.setAdapter(mAdapter); - - mEmptyView.setOnLayoutClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - fristSendGetUserInfomation(); - } - }); - } - - private void fristSendGetUserInfomation() { - mState = STATE_REFRESH; - mListView.setVisibility(View.GONE); - mEmptyView.setErrorType(EmptyLayout.NETWORK_LOADING); - sendGetUserInfomation(); - } - - private void sendGetUserInfomation() { - OSChinaApi.getUserInformation(mUid, mHisUid, mHisName, mActivePage, - mActiveHandler); - } - - private void fillUI() { - mListView.setVisibility(View.VISIBLE); - ((AvatarView) mIvAvatar).setAvatarUrl(mUser.getPortrait()); - mHisUid = mUser.getId(); - mHisName = mUser.getName(); - mTvName.setText(mHisName); - - int genderIcon = R.drawable.userinfo_icon_male; - if (FEMALE.equals(mUser.getGender())) { - genderIcon = R.drawable.userinfo_icon_female; - } - mIvGender.setBackgroundResource(genderIcon); - - mTvFollowing.setText(mUser.getFollowers() + ""); - mTvFollower.setText(mUser.getFans() + ""); - mTvSore.setText(mUser.getScore() + ""); - mTvLastestLoginTime.setText(getString(R.string.latest_login_time, - StringUtils.friendly_time(mUser.getLatestonline()))); - updateUserRelation(); - } - - private void updateUserRelation() { - switch (mUser.getRelation()) { - case User.RELATION_TYPE_BOTH: - mBtnFollowUser.setCompoundDrawablesWithIntrinsicBounds( - R.drawable.ic_follow_each_other, 0, 0, 0); - mBtnFollowUser.setText(R.string.follow_each_other); - mBtnFollowUser.setTextColor(getResources().getColor(R.color.black)); - mBtnFollowUser - .setBackgroundResource(R.drawable.btn_small_white_selector); - break; - case User.RELATION_TYPE_FANS_HIM: - mBtnFollowUser.setCompoundDrawablesWithIntrinsicBounds( - R.drawable.ic_followed, 0, 0, 0); - mBtnFollowUser.setText(R.string.unfollow_user); - mBtnFollowUser.setTextColor(getResources().getColor(R.color.black)); - mBtnFollowUser - .setBackgroundResource(R.drawable.btn_small_white_selector); - break; - case User.RELATION_TYPE_FANS_ME: - mBtnFollowUser.setCompoundDrawablesWithIntrinsicBounds( - R.drawable.ic_add_follow, 0, 0, 0); - mBtnFollowUser.setText(R.string.follow_user); - mBtnFollowUser.setTextColor(getResources().getColor(R.color.white)); - mBtnFollowUser - .setBackgroundResource(R.drawable.btn_small_green_selector); - break; - case User.RELATION_TYPE_NULL: - mBtnFollowUser.setCompoundDrawablesWithIntrinsicBounds( - R.drawable.ic_add_follow, 0, 0, 0); - mBtnFollowUser.setText(R.string.follow_user); - mBtnFollowUser.setTextColor(getResources().getColor(R.color.white)); - mBtnFollowUser - .setBackgroundResource(R.drawable.btn_small_green_selector); - break; - } - int padding = (int) TDevice.dpToPixel(20); - mBtnFollowUser.setPadding(padding, 0, padding, 0); - } - - private AlertDialog mInformationDialog; - - private void showInformationDialog() { - if (mInformationDialog == null) { - mInformationDialog = DialogHelp.getDialog(getActivity()).show(); - View view = LayoutInflater.from(getActivity()).inflate( - R.layout.fragment_user_center_information, null); - ((TextView) view.findViewById(R.id.tv_join_time)) - .setText(StringUtils.friendly_time(mUser.getJointime())); - ((TextView) view.findViewById(R.id.tv_location)) - .setText(StringUtils.getString(mUser.getFrom())); - ((TextView) view.findViewById(R.id.tv_development_platform)) - .setText(StringUtils.getString(mUser.getDevplatform())); - ((TextView) view.findViewById(R.id.tv_academic_focus)) - .setText(StringUtils.getString(mUser.getExpertise())); - mInformationDialog.setContentView(view); - } - - mInformationDialog.show(); - } - - private void handleUserRelation() { - if (mUser == null) - return; - // 判断登录 - final AppContext ac = AppContext.getInstance(); - if (!ac.isLogin()) { - UIHelper.showLoginActivity(getActivity()); - return; - } - String dialogTitle = ""; - int relationAction = 0; - switch (mUser.getRelation()) { - case User.RELATION_TYPE_BOTH: - dialogTitle = "确定取消互粉吗?"; - relationAction = User.RELATION_ACTION_DELETE; - break; - case User.RELATION_TYPE_FANS_HIM: - dialogTitle = "确定取消关注吗?"; - relationAction = User.RELATION_ACTION_DELETE; - break; - case User.RELATION_TYPE_FANS_ME: - dialogTitle = "确定关注Ta吗?"; - relationAction = User.RELATION_ACTION_ADD; - break; - case User.RELATION_TYPE_NULL: - dialogTitle = "确定关注Ta吗?"; - relationAction = User.RELATION_ACTION_ADD; - break; - } - final int ra = relationAction; - - DialogHelp.getConfirmDialog(getActivity(), dialogTitle, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - sendUpdateRelcationRequest(ra); - } - }).show(); - } - - private void sendUpdateRelcationRequest(int ra) { - OSChinaApi.updateRelation(mUid, mHisUid, ra, - new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - try { - Result result = XmlUtils.toBean(ResultBean.class, - new ByteArrayInputStream(arg2)).getResult(); - if (result.OK()) { - switch (mUser.getRelation()) { - case User.RELATION_TYPE_BOTH: - mBtnFollowUser - .setCompoundDrawablesWithIntrinsicBounds( - R.drawable.ic_add_follow, - 0, 0, 0); - mBtnFollowUser - .setText(R.string.follow_user); - mBtnFollowUser.setTextColor(getResources() - .getColor(R.color.white)); - mBtnFollowUser - .setBackgroundResource(R.drawable.btn_small_green_selector); - mUser.setRelation(User.RELATION_TYPE_FANS_ME); - break; - case User.RELATION_TYPE_FANS_HIM: - mBtnFollowUser - .setCompoundDrawablesWithIntrinsicBounds( - R.drawable.ic_add_follow, - 0, 0, 0); - mBtnFollowUser - .setText(R.string.follow_user); - mBtnFollowUser.setTextColor(getResources() - .getColor(R.color.white)); - mBtnFollowUser - .setBackgroundResource(R.drawable.btn_small_green_selector); - mUser.setRelation(User.RELATION_TYPE_NULL); - break; - case User.RELATION_TYPE_FANS_ME: - mBtnFollowUser - .setCompoundDrawablesWithIntrinsicBounds( - R.drawable.ic_followed, 0, - 0, 0); - mBtnFollowUser - .setText(R.string.follow_each_other); - mBtnFollowUser.setTextColor(getResources() - .getColor(R.color.black)); - mBtnFollowUser - .setBackgroundResource(R.drawable.btn_small_white_selector); - mUser.setRelation(User.RELATION_TYPE_BOTH); - break; - case User.RELATION_TYPE_NULL: - mBtnFollowUser - .setCompoundDrawablesWithIntrinsicBounds( - R.drawable.ic_followed, 0, - 0, 0); - mBtnFollowUser - .setText(R.string.unfollow_user); - mBtnFollowUser.setTextColor(getResources() - .getColor(R.color.black)); - mBtnFollowUser - .setBackgroundResource(R.drawable.btn_small_white_selector); - mUser.setRelation(User.RELATION_TYPE_FANS_HIM); - break; - } - int padding = (int) TDevice.dpToPixel(20); - mBtnFollowUser.setPadding(padding, 0, padding, - 0); - } - AppContext.showToastShort(result.getErrorMessage()); - } catch (Exception e) { - e.printStackTrace(); - onFailure(arg0, arg1, arg2, e); - } - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) {} - }); - } - - @Override - public void initData() {} - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - if (position - 1 < 0) { - return; - } - Active active = (Active) mAdapter.getItem(position - 1); - if (active != null) - UIHelper.showActiveRedirect(view.getContext(), active); - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { - // 数据已经全部加载,或数据为空时,或正在加载,不处理滚动事件 - if (mState == STATE_NOMORE || mState == STATE_LOADMORE - || mState == STATE_REFRESH) { - return; - } - if (mAdapter != null - && mAdapter.getDataSize() > 0 - && mListView.getLastVisiblePosition() == (mListView.getCount() - 1)) { - if (mState == STATE_NONE - && mAdapter.getState() == ListBaseAdapter.STATE_LOAD_MORE) { - mState = STATE_LOADMORE; - mActivePage++; - sendGetUserInfomation(); - } - } - } - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) {} -} diff --git a/app/src/main/java/net/oschina/app/fragment/UserFavoriteFragment.java b/app/src/main/java/net/oschina/app/fragment/UserFavoriteFragment.java deleted file mode 100644 index aae6ba005ae13aaaedd698db03257ddcf28d1c13..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/fragment/UserFavoriteFragment.java +++ /dev/null @@ -1,79 +0,0 @@ -package net.oschina.app.fragment; - -import java.io.InputStream; -import java.io.Serializable; - -import net.oschina.app.AppContext; -import net.oschina.app.adapter.UserFavoriteAdapter; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.BaseListFragment; -import net.oschina.app.bean.Favorite; -import net.oschina.app.bean.FavoriteList; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; -import android.view.View; -import android.widget.AdapterView; - -public class UserFavoriteFragment extends BaseListFragment { - - protected static final String TAG = UserFavoriteFragment.class - .getSimpleName(); - private static final String CACHE_KEY_PREFIX = "userfavorite_"; - - @Override - protected UserFavoriteAdapter getListAdapter() { - return new UserFavoriteAdapter(); - } - - @Override - protected String getCacheKeyPrefix() { - return CACHE_KEY_PREFIX + mCatalog; - } - - @Override - protected FavoriteList parseList(InputStream is) throws Exception { - - FavoriteList list = XmlUtils.toBean(FavoriteList.class, is); - return list; - } - - @Override - protected FavoriteList readList(Serializable seri) { - return ((FavoriteList) seri); - } - - @Override - protected void sendRequestData() { - OSChinaApi.getFavoriteList(AppContext.getInstance().getLoginUid(), - mCatalog, mCurrentPage, mHandler); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - - Favorite favorite = (Favorite) mAdapter.getItem(position); - if (favorite != null) { - switch (favorite.getType()) { - - case Favorite.CATALOG_BLOGS: - UIHelper.showUrlRedirect(getActivity(), favorite.getUrl()); - break; - case Favorite.CATALOG_CODE: - UIHelper.showUrlRedirect(getActivity(), favorite.getUrl()); - break; - case Favorite.CATALOG_NEWS: - UIHelper.showUrlRedirect(getActivity(), favorite.getUrl()); - break; - case Favorite.CATALOG_SOFTWARE: - UIHelper.showUrlRedirect(getActivity(), favorite.getUrl()); - break; - case Favorite.CATALOG_TOPIC: - UIHelper.showUrlRedirect(getActivity(), favorite.getUrl()); - break; - - } - } - - } -} diff --git a/app/src/main/java/net/oschina/app/improve/account/AccountHelper.java b/app/src/main/java/net/oschina/app/improve/account/AccountHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..f2171cc52df67aab561d316e78c2f73576879229 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/account/AccountHelper.java @@ -0,0 +1,161 @@ +package net.oschina.app.improve.account; + +import android.annotation.SuppressLint; +import android.app.Application; +import android.content.Intent; +import android.os.SystemClock; +import android.support.v4.content.LocalBroadcastManager; +import android.text.TextUtils; +import android.view.View; + +import net.oschina.app.api.ApiHttpClient; +import net.oschina.app.bean.Constants; +import net.oschina.app.cache.CacheManager; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.notice.NoticeManager; +import net.oschina.app.improve.tweet.fragments.TweetFragment; +import net.oschina.app.util.TLog; +import net.oschina.common.helper.SharedPreferencesHelper; + +import cz.msebera.android.httpclient.Header; + +/** + * 账户辅助类, + * 用于更新用户信息和保存当前账户等操作 + */ +public final class AccountHelper { + private static final String TAG = AccountHelper.class.getSimpleName(); + private User user; + private Application application; + @SuppressLint("StaticFieldLeak") + private static AccountHelper instances; + + private AccountHelper(Application application) { + this.application = application; + } + + public static void init(Application application) { + if (instances == null) + instances = new AccountHelper(application); + else { + // reload from source + instances.user = SharedPreferencesHelper.loadFormSource(instances.application, User.class); + TLog.d(TAG, "init reload:" + instances.user); + } + } + + public static boolean isLogin() { + return getUserId() > 0 && !TextUtils.isEmpty(getCookie()); + } + + public static String getCookie() { + String cookie = getUser().getCookie(); + return cookie == null ? "" : cookie; + } + + public static long getUserId() { + return getUser().getId(); + } + + public synchronized static User getUser() { + if (instances == null) { + TLog.error("AccountHelper instances is null, you need call init() method."); + return new User(); + } + if (instances.user == null) + instances.user = SharedPreferencesHelper.loadFormSource(instances.application, User.class); + if (instances.user == null) + instances.user = new User(); + return instances.user; + } + + public static boolean updateUserCache(User user) { + if (user == null) + return false; + // 保留Cookie信息 + if (TextUtils.isEmpty(user.getCookie()) && instances.user != user) + user.setCookie(instances.user.getCookie()); + instances.user = user; + return SharedPreferencesHelper.save(instances.application, user); + } + + private static void clearUserCache() { + instances.user = null; + SharedPreferencesHelper.remove(instances.application, User.class); + } + + public static boolean login(final User user, Header[] headers) { + // 更新Cookie + String cookie = ApiHttpClient.getCookie(headers); + if (TextUtils.isEmpty(cookie) || cookie.length() < 6) { + return false; + } + + TLog.d(TAG, "login:" + user + " cookie:" + cookie); + + user.setCookie(cookie); + + int count = 10; + boolean saveOk; + // 保存缓存 + while (!(saveOk = updateUserCache(user)) && count-- > 0) { + SystemClock.sleep(100); + } + + if (saveOk) { + ApiHttpClient.setCookieHeader(getCookie()); + // 登陆成功,重新启动消息服务 + NoticeManager.init(instances.application); + } + return saveOk; + } + + /** + * 退出登陆操作需要传递一个View协助完成延迟检测操作 + * + * @param view View + * @param runnable 当清理完成后回调方法 + */ + public static void logout(final View view, final Runnable runnable) { + // 清除用户缓存 + clearUserCache(); + // 等待缓存清理完成 + view.postDelayed(new Runnable() { + @Override + public void run() { + view.removeCallbacks(this); + User user = SharedPreferencesHelper.load(instances.application, User.class); + // 判断当前用户信息是否清理成功 + if (user == null || user.getId() <= 0) { + clearAndPostBroadcast(instances.application); + runnable.run(); + } else { + view.postDelayed(this, 200); + } + } + }, 200); + + } + + /** + * 当前用户信息清理完成后调用方法清理服务等信息 + * + * @param application Application + */ + private static void clearAndPostBroadcast(Application application) { + // 清理网络相关 + ApiHttpClient.destroyAndRestore(application); + + // 用户退出时清理红点并退出服务 + NoticeManager.clear(application, NoticeManager.FLAG_CLEAR_ALL); + NoticeManager.exitServer(application); + + // 清理动弹对应数据 + CacheManager.deleteObject(application, TweetFragment.CACHE_USER_TWEET); + + // Logout 广播 + Intent intent = new Intent(Constants.INTENT_ACTION_LOGOUT); + LocalBroadcastManager.getInstance(application).sendBroadcast(intent); + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/account/activity/CsdnLoginActivity.java b/app/src/main/java/net/oschina/app/improve/account/activity/CsdnLoginActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..6397d218101ef1360a9be76ef138d1993d099a1c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/account/activity/CsdnLoginActivity.java @@ -0,0 +1,133 @@ +package net.oschina.app.improve.account.activity; + +import android.app.Activity; +import android.content.Intent; +import android.support.v7.widget.AppCompatEditText; +import android.text.TextUtils; +import android.view.View; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.activities.BaseBackActivity; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.TDevice; + +import java.io.Serializable; +import java.lang.reflect.Type; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * CSDN第三方登陆界面,非Oauth + * Created by huanghaibin on 2017/8/8. + */ + +public class CsdnLoginActivity extends BaseBackActivity implements View.OnClickListener { + + @Bind(R.id.et_nickname) + AppCompatEditText mEditNickname; + @Bind(R.id.et_pwd) + AppCompatEditText mEditPwd; + + public static void show(Activity activity) { + Intent intent = new Intent(activity, CsdnLoginActivity.class); + activity.startActivityForResult(intent, 5); + } + + @Override + protected int getContentView() { + return R.layout.activity_csdn_login; + } + + @Override + public void onClick(View v) { + String name = mEditNickname.getText().toString().trim().replace(" ", ""); + String pwd = mEditPwd.getText().toString().trim().replace(" ", ""); + if (TextUtils.isEmpty(name)) { + SimplexToast.show(this, "用户名不能为空"); + return; + } + if (TextUtils.isEmpty(pwd)) { + SimplexToast.show(this, "密码不能为空"); + return; + } + showLoadingDialog("正在授权登陆"); + OSChinaApi.csdnLogin(name, pwd, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (isDestroy()) + return; + dismissLoadingDialog(); + SimplexToast.show(CsdnLoginActivity.this, "网络错误"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + TDevice.closeKeyboard(mEditPwd); + Type type = new TypeToken() { + }.getRawType(); + Oauth oauth = new Gson().fromJson(responseString, type); + if (oauth == null || oauth.getAccess_token() == null) { + SimplexToast.show(CsdnLoginActivity.this, "授权失败"); + return; + } + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("openid", oauth.getUsername()); + jsonObject.addProperty("expires_in", oauth.getExpires_in()); + jsonObject.addProperty("access_token", oauth.getAccess_token()); + Intent intent = new Intent(); + intent.putExtra("json", jsonObject.toString()); + setResult(RESULT_OK, intent); + finish(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onFinish() { + super.onFinish(); + dismissLoadingDialog(); + } + }); + } + + @SuppressWarnings("all") + public static class Oauth implements Serializable { + private int expires_in; + private String access_token; + private String username; + + public int getExpires_in() { + return expires_in; + } + + public void setExpires_in(int expires_in) { + this.expires_in = expires_in; + } + + public String getAccess_token() { + return access_token; + } + + public void setAccess_token(String access_token) { + this.access_token = access_token; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/account/activity/LoginActivity.java b/app/src/main/java/net/oschina/app/improve/account/activity/LoginActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..600fc87f42783e5643dbcd886e883a5c0215269b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/account/activity/LoginActivity.java @@ -0,0 +1,853 @@ +package net.oschina.app.improve.account.activity; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Rect; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.content.SharedPreferencesCompat; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.animation.DecelerateInterpolator; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; +import com.sina.weibo.sdk.auth.Oauth2AccessToken; +import com.sina.weibo.sdk.auth.WeiboAuthListener; +import com.sina.weibo.sdk.auth.sso.SsoHandler; +import com.sina.weibo.sdk.exception.WeiboException; +import com.tencent.tauth.IUiListener; +import com.tencent.tauth.Tencent; +import com.tencent.tauth.UiError; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.base.AccountBaseActivity; +import net.oschina.app.improve.account.constants.UserConstants; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.user.helper.ContactsCacheManager; +import net.oschina.app.util.TDevice; +import net.oschina.open.constants.OpenConstant; +import net.oschina.open.factory.OpenBuilder; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import butterknife.OnClick; +import cz.msebera.android.httpclient.Header; + + +/** + * Created by fei on 2016/10/14. + * desc: + */ + +public class LoginActivity extends AccountBaseActivity implements View.OnClickListener, IUiListener, View.OnFocusChangeListener, ViewTreeObserver.OnGlobalLayoutListener { + + public static final String HOLD_USERNAME_KEY = "holdUsernameKey"; + + @Bind(R.id.ly_retrieve_bar) + LinearLayout mLayBackBar; + + @Bind(R.id.iv_login_logo) + ImageView mIvLoginLogo; + + @Bind(R.id.ll_login_username) + LinearLayout mLlLoginUsername; + @Bind(R.id.et_login_username) + EditText mEtLoginUsername; + @Bind(R.id.iv_login_username_del) + ImageView mIvLoginUsernameDel; + + @Bind(R.id.ll_login_pwd) + LinearLayout mLlLoginPwd; + @Bind(R.id.et_login_pwd) + EditText mEtLoginPwd; + @Bind(R.id.iv_login_pwd_del) + ImageView mIvLoginPwdDel; + + @Bind(R.id.iv_login_hold_pwd) + ImageView mIvHoldPwd; + @Bind(R.id.tv_login_forget_pwd) + TextView mTvLoginForgetPwd; + + @Bind(R.id.bt_login_submit) + Button mBtLoginSubmit; + @Bind(R.id.bt_login_register) + Button mBtLoginRegister; + + @Bind(R.id.ll_login_layer) + View mLlLoginLayer; + @Bind(R.id.ll_login_pull) + LinearLayout mLlLoginPull; + + @Bind(R.id.ll_login_options) + LinearLayout mLlLoginOptions; + + @Bind(R.id.ib_login_weibo) + ImageView mIbLoginWeiBo; + @Bind(R.id.ib_login_wx) + ImageView mIbLoginWx; + @Bind(R.id.ib_login_qq) + ImageView mImLoginQq; + + private int openType; + private SsoHandler mSsoHandler; + private Tencent mTencent; + + + private TextHttpResponseHandler mHandler = new TextHttpResponseHandler() { + + @Override + public void onStart() { + super.onStart(); + showFocusWaitDialog(); + } + + @Override + public void onFinish() { + super.onFinish(); + hideWaitDialog(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + requestFailureHint(throwable); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + Type type = new TypeToken>() { + }.getType(); + + GsonBuilder gsonBuilder = new GsonBuilder(); + ResultBean resultBean = gsonBuilder.create().fromJson(responseString, type); + if (resultBean.isSuccess()) { + User user = resultBean.getResult(); + if (AccountHelper.login(user, headers)) { + logSucceed(); + } else { + showToastForKeyBord("登录异常"); + } + } else { + showToastForKeyBord(resultBean.getMessage()); + } + } + }; + + private void logSucceed() { + View view; + if ((view = getCurrentFocus()) != null) { + hideKeyBoard(view.getWindowToken()); + } + AppContext.showToast(R.string.login_success_hint); + setResult(RESULT_OK); + sendLocalReceiver(); + //后台异步同步数据 + ContactsCacheManager.sync(); + holdAccount(); + } + + private int mLogoHeight; + private int mLogoWidth; + + /** + * hold account information + */ + private void holdAccount() { + String username = mEtLoginUsername.getText().toString().trim(); + if (!TextUtils.isEmpty(username)) { + SharedPreferences sp = getSharedPreferences(UserConstants.HOLD_ACCOUNT, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sp.edit(); + editor.putString(HOLD_USERNAME_KEY, username); + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); + } + } + + /** + * show the login activity + * + * @param context context + */ + public static void show(Context context) { + Intent intent = new Intent(context, LoginActivity.class); + context.startActivity(intent); + } + + /** + * show the login activity + * + * @param context context + */ + public static void show(Activity context, int requestCode) { + Intent intent = new Intent(context, LoginActivity.class); + context.startActivityForResult(intent, requestCode); + } + + /** + * show the login activity + * + * @param fragment fragment + */ + public static void show(Fragment fragment, int requestCode) { + Intent intent = new Intent(fragment.getActivity(), LoginActivity.class); + fragment.startActivityForResult(intent, requestCode); + } + + @Override + protected int getContentView() { + return R.layout.activity_main_login; + } + + @Override + protected void initWidget() { + super.initWidget(); + mLlLoginLayer.setVisibility(View.GONE); + mEtLoginUsername.setOnFocusChangeListener(this); + mEtLoginUsername.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @SuppressWarnings("deprecation") + @Override + public void afterTextChanged(Editable s) { + String username = s.toString().trim(); + if (username.length() > 0) { + mLlLoginUsername.setBackgroundResource(R.drawable.bg_login_input_ok); + mIvLoginUsernameDel.setVisibility(View.VISIBLE); + } else { + mLlLoginUsername.setBackgroundResource(R.drawable.bg_login_input_ok); + mIvLoginUsernameDel.setVisibility(View.INVISIBLE); + } + + String pwd = mEtLoginPwd.getText().toString().trim(); + if (!TextUtils.isEmpty(pwd)) { + mBtLoginSubmit.setBackgroundResource(R.drawable.bg_login_submit); + mBtLoginSubmit.setTextColor(getResources().getColor(R.color.white)); + } else { + mBtLoginSubmit.setBackgroundResource(R.drawable.bg_login_submit_lock); + mBtLoginSubmit.setTextColor(getResources().getColor(R.color.account_lock_font_color)); + } + + } + }); + + mEtLoginPwd.setOnFocusChangeListener(this); + mEtLoginPwd.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @SuppressWarnings("deprecation") + @Override + public void afterTextChanged(Editable s) { + int length = s.length(); + if (length > 0) { + mLlLoginPwd.setBackgroundResource(R.drawable.bg_login_input_ok); + mIvLoginPwdDel.setVisibility(View.VISIBLE); + } else { + mIvLoginPwdDel.setVisibility(View.INVISIBLE); + } + + String username = mEtLoginUsername.getText().toString().trim(); + if (TextUtils.isEmpty(username)) { + showToastForKeyBord(R.string.message_username_null); + } + String pwd = mEtLoginPwd.getText().toString().trim(); + if (!TextUtils.isEmpty(pwd)) { + mBtLoginSubmit.setBackgroundResource(R.drawable.bg_login_submit); + mBtLoginSubmit.setTextColor(getResources().getColor(R.color.white)); + } else { + mBtLoginSubmit.setBackgroundResource(R.drawable.bg_login_submit_lock); + mBtLoginSubmit.setTextColor(getResources().getColor(R.color.account_lock_font_color)); + } + } + }); + + TextView label = (TextView) mLayBackBar.findViewById(R.id.tv_navigation_label); + label.setVisibility(View.INVISIBLE); + + } + + @Override + protected void initData() { + super.initData();//必须要,用来注册本地广播 + + //初始化控件状态数据 + SharedPreferences sp = getSharedPreferences(UserConstants.HOLD_ACCOUNT, Context.MODE_PRIVATE); + String holdUsername = sp.getString(HOLD_USERNAME_KEY, null); + //String holdPwd = sp.getString(HOLD_PWD_KEY, null); + //int holdStatus = sp.getInt(HOLD_PWD_STATUS_KEY, 0);//0第一次默认/1用户设置保存/2用户设置未保存 + + mEtLoginUsername.setText(holdUsername); + + } + + @Override + protected void onResume() { + super.onResume(); + mLayBackBar.getViewTreeObserver().addOnGlobalLayoutListener(this); + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void onDestroy() { + super.onDestroy(); + try { + hideKeyBoard(getCurrentFocus().getWindowToken()); + }catch (Exception e){ + e.printStackTrace(); + } + mLayBackBar.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + + @SuppressWarnings("ConstantConditions") + @OnClick({R.id.ib_navigation_back, R.id.et_login_username, R.id.et_login_pwd, R.id.tv_login_forget_pwd, + R.id.iv_login_hold_pwd, R.id.bt_login_submit, R.id.bt_login_register, R.id.ll_login_pull, R.id.ib_login_csdn, + R.id.ib_login_weibo, R.id.ib_login_wx, R.id.ib_login_qq, R.id.ll_login_layer, + R.id.iv_login_username_del, R.id.iv_login_pwd_del, R.id.lay_login_container}) + @Override + public void onClick(View v) { + int id = v.getId(); + switch (id) { + case R.id.ib_navigation_back: + finish(); + break; + case R.id.et_login_username: + mEtLoginPwd.clearFocus(); + mEtLoginUsername.setFocusableInTouchMode(true); + mEtLoginUsername.requestFocus(); + break; + case R.id.et_login_pwd: + mEtLoginUsername.clearFocus(); + mEtLoginPwd.setFocusableInTouchMode(true); + mEtLoginPwd.requestFocus(); + break; + case R.id.tv_login_forget_pwd: + //忘记密码 + RetrieveActivity.show(LoginActivity.this); + break; + case R.id.bt_login_submit: + //用户登录 + loginRequest(); + break; + case R.id.iv_login_hold_pwd: + //记住密码 + case R.id.bt_login_register: + RegisterStepOneActivity.show(LoginActivity.this); + break; + case R.id.ll_login_layer: + case R.id.ll_login_pull: + + mLlLoginPull.animate().cancel(); + mLlLoginLayer.animate().cancel(); + + int height = mLlLoginOptions.getHeight(); + float progress = (mLlLoginLayer.getTag() != null && mLlLoginLayer.getTag() instanceof Float) ? + (float) mLlLoginLayer.getTag() : 1; + int time = (int) (360 * progress); + + if (mLlLoginPull.getTag() != null) { + mLlLoginPull.setTag(null); + glide(height, progress, time); + } else { + mLlLoginPull.setTag(true); + upGlide(height, progress, time); + } + break; + case R.id.ib_login_weibo: + weiBoLogin(); + break; + case R.id.ib_login_wx: + //微信登录 + wechatLogin(); + break; + case R.id.ib_login_qq: + //QQ登录 + tencentLogin(); + break; + case R.id.ib_login_csdn: + //csdn登录 + CsdnLoginActivity.show(this); + break; + case R.id.iv_login_username_del: + mEtLoginUsername.setText(null); + break; + case R.id.iv_login_pwd_del: + mEtLoginPwd.setText(null); + break; + case R.id.lay_login_container: + try { + hideKeyBoard(getCurrentFocus().getWindowToken()); + }catch (Exception e){ + e.printStackTrace(); + } + break; + default: + break; + } + + } + + /** + * login tencent + */ + private void tencentLogin() { + showWaitDialog(R.string.login_tencent_hint); + openType = OpenConstant.TENCENT; + mTencent = OpenBuilder.with(this) + .useTencent(OpenConstant.QQ_APP_ID) + .login(this, new OpenBuilder.Callback() { + @Override + public void onFailed() { + hideWaitDialog(); + } + + @Override + public void onSuccess() { + //hideWaitDialog(); + } + }); + } + + /** + * login wechat + */ + private void wechatLogin() { + showWaitDialog(R.string.login_wechat_hint); + openType = OpenConstant.WECHAT; + OpenBuilder.with(this) + .useWechat(OpenConstant.WECHAT_APP_ID) + .login(new OpenBuilder.Callback() { + @Override + public void onFailed() { + hideWaitDialog(); + AppContext.showToast(R.string.login_hint); + } + + @Override + public void onSuccess() { + //hideWaitDialog(); + } + }); + } + + /** + * login weiBo + */ + private void weiBoLogin() { + showWaitDialog(R.string.login_weibo_hint); + openType = OpenConstant.SINA; + mSsoHandler = OpenBuilder.with(this) + .useWeibo(OpenConstant.WB_APP_KEY) + .login(new WeiboAuthListener() { + @Override + public void onComplete(Bundle bundle) { + hideWaitDialog(); + Oauth2AccessToken oauth2AccessToken = Oauth2AccessToken.parseAccessToken(bundle); + + if (oauth2AccessToken.isSessionValid()) { + try { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("openid", oauth2AccessToken.getUid()); + jsonObject.put("expires_in", oauth2AccessToken.getExpiresTime()); + jsonObject.put("refresh_token", oauth2AccessToken.getRefreshToken()); + jsonObject.put("access_token", oauth2AccessToken.getToken()); + + OSChinaApi.openLogin(OSChinaApi.LOGIN_WEIBO, jsonObject.toString(), mHandler); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + + @Override + public void onWeiboException(WeiboException e) { + e.printStackTrace(); + hideWaitDialog(); + } + + @Override + public void onCancel() { + hideWaitDialog(); + } + }); + } + + + /** + * menu up glide + * + * @param height height + * @param progress progress + * @param time time + */ + private void upGlide(int height, float progress, int time) { + mLlLoginPull.animate() + .translationYBy(height * progress) + .translationY(0) + .setDuration(time) + .start(); + mLlLoginLayer.animate() + .alphaBy(1 - progress) + .alpha(1) + .setDuration(time) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mLlLoginLayer.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationCancel(Animator animation) { + if (animation instanceof ValueAnimator) { + mLlLoginLayer.setTag(((ValueAnimator) animation).getAnimatedValue()); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (animation instanceof ValueAnimator) { + mLlLoginLayer.setTag(((ValueAnimator) animation).getAnimatedValue()); + } + } + }) + .start(); + } + + /** + * menu glide + * + * @param height height + * @param progress progress + * @param time time + */ + private void glide(int height, float progress, int time) { + mLlLoginPull.animate() + .translationYBy(height - height * progress) + .translationY(height) + .setDuration(time) + .start(); + + mLlLoginLayer.animate() + .alphaBy(1 * progress) + .alpha(0) + .setDuration(time) + .setListener(new AnimatorListenerAdapter() { + + @Override + public void onAnimationCancel(Animator animation) { + if (animation instanceof ValueAnimator) { + mLlLoginLayer.setTag(((ValueAnimator) animation).getAnimatedValue()); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (animation instanceof ValueAnimator) { + mLlLoginLayer.setTag(((ValueAnimator) animation).getAnimatedValue()); + } + mLlLoginLayer.setVisibility(View.GONE); + } + }) + .start(); + } + + @SuppressWarnings("ConstantConditions") + private void loginRequest() { + + String tempUsername = mEtLoginUsername.getText().toString().trim(); + String tempPwd = mEtLoginPwd.getText().toString().trim(); + + + if (!TextUtils.isEmpty(tempPwd) && !TextUtils.isEmpty(tempUsername)) { + //登录成功,请求数据进入用户个人中心页面 + + if (TDevice.hasInternet()) { + requestLogin(tempUsername, tempPwd); + } else { + showToastForKeyBord(R.string.footer_type_net_error); + } + + } else { + showToastForKeyBord(R.string.login_input_username_hint_error); + } + + } + + private void requestLogin(String tempUsername, String tempPwd) { + OSChinaApi.login(tempUsername, getSha1(tempPwd), new TextHttpResponseHandler() { + + @Override + public void onStart() { + super.onStart(); + showFocusWaitDialog(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + requestFailureHint(throwable); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + try { + Type type = new TypeToken>() { + }.getType(); + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + User user = resultBean.getResult(); + if (AccountHelper.login(user, headers)) { + logSucceed(); + } else { + showToastForKeyBord("登录异常"); + } + } else { + int code = resultBean.getCode(); + String message = resultBean.getMessage(); + if (code == 211) { + mEtLoginPwd.setFocusableInTouchMode(false); + mEtLoginPwd.clearFocus(); + mEtLoginUsername.requestFocus(); + mEtLoginUsername.setFocusableInTouchMode(true); + mLlLoginUsername.setBackgroundResource(R.drawable.bg_login_input_error); + } else if (code == 212) { + mEtLoginUsername.setFocusableInTouchMode(false); + mEtLoginUsername.clearFocus(); + mEtLoginPwd.requestFocus(); + mEtLoginPwd.setFocusableInTouchMode(true); + message += "," + getResources().getString(R.string.message_pwd_error); + mLlLoginPwd.setBackgroundResource(R.drawable.bg_login_input_error); + } + showToastForKeyBord(message); + //更新失败应该是不进行任何的本地操作 + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + + @Override + public void onFinish() { + super.onFinish(); + hideWaitDialog(); + } + + @Override + public void onCancel() { + super.onCancel(); + hideWaitDialog(); + } + }); + } + + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == 5 && resultCode == RESULT_OK && data != null) { + csdnLogin(data.getStringExtra("json")); + return; + } + tencentOnActivityResult(data); + weiBoOnActivityResult(requestCode, resultCode, data); + + super.onActivityResult(requestCode, resultCode, data); + } + + private void csdnLogin(String json) { + OSChinaApi.openLogin(OSChinaApi.LOGIN_CSDN, json, mHandler); + } + + /** + * weiBo Activity Result + * + * @param requestCode requestCode + * @param resultCode resultCode + * @param data data + */ + + private void weiBoOnActivityResult(int requestCode, int resultCode, Intent data) { + if (openType == OpenConstant.SINA) { + // SSO 授权回调 + // 重要:发起 SSO 登陆的 Activity 必须重写 onActivityResults + if (mSsoHandler != null) + mSsoHandler.authorizeCallBack(requestCode, resultCode, data); + } + } + + /** + * tencent Activity Result + * + * @param data data + */ + @SuppressWarnings("deprecation") + private void tencentOnActivityResult(Intent data) { + if (openType == OpenConstant.TENCENT) { + // 对于tencent + // 注:在某些低端机上调用登录后,由于内存紧张导致APP被系统回收,登录成功后无法成功回传数据。 + if (mTencent != null) { + mTencent.handleLoginData(data, this); + } + } + } + + /** + * tencent callback + * + * @param o json + */ + @Override + public void onComplete(Object o) { + JSONObject jsonObject = (JSONObject) o; + OSChinaApi.openLogin(OSChinaApi.LOGIN_QQ, jsonObject.toString(), mHandler); + hideWaitDialog(); + } + + /** + * tencent callback + * + * @param uiError uiError + */ + @Override + public void onError(UiError uiError) { + hideWaitDialog(); + } + + + /** + * tencent callback + */ + @Override + public void onCancel() { + hideWaitDialog(); + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + + int id = v.getId(); + + if (id == R.id.et_login_username) { + if (hasFocus) { + mLlLoginUsername.setActivated(true); + mLlLoginPwd.setActivated(false); + } + } else { + if (hasFocus) { + mLlLoginPwd.setActivated(true); + mLlLoginUsername.setActivated(false); + } + } + } + + + @Override + public void onGlobalLayout() { + + final ImageView ivLogo = this.mIvLoginLogo; + Rect KeypadRect = new Rect(); + + mLayBackBar.getWindowVisibleDisplayFrame(KeypadRect); + + int screenHeight = mLayBackBar.getRootView().getHeight(); + + int keypadHeight = screenHeight - KeypadRect.bottom; + + if (keypadHeight > 0) { + updateKeyBoardActiveStatus(true); + } else { + updateKeyBoardActiveStatus(false); + } + if (keypadHeight > 0 && ivLogo.getTag() == null) { + final int height = ivLogo.getHeight(); + final int width = ivLogo.getWidth(); + this.mLogoHeight = height; + this.mLogoWidth = width; + ivLogo.setTag(true); + ValueAnimator valueAnimator = ValueAnimator.ofFloat(1, 0); + valueAnimator.setDuration(400).setInterpolator(new DecelerateInterpolator()); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float animatedValue = (float) animation.getAnimatedValue(); + ViewGroup.LayoutParams layoutParams = ivLogo.getLayoutParams(); + layoutParams.height = (int) (height * animatedValue); + layoutParams.width = (int) (width * animatedValue); + ivLogo.requestLayout(); + ivLogo.setAlpha(animatedValue); + } + }); + + if (valueAnimator.isRunning()) { + valueAnimator.cancel(); + } + valueAnimator.start(); + + + } else if (keypadHeight == 0 && ivLogo.getTag() != null) { + final int height = mLogoHeight; + final int width = mLogoWidth; + ivLogo.setTag(null); + ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); + valueAnimator.setDuration(400).setInterpolator(new DecelerateInterpolator()); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float animatedValue = (float) animation.getAnimatedValue(); + ViewGroup.LayoutParams layoutParams = ivLogo.getLayoutParams(); + layoutParams.height = (int) (height * animatedValue); + layoutParams.width = (int) (width * animatedValue); + ivLogo.requestLayout(); + ivLogo.setAlpha(animatedValue); + } + }); + + if (valueAnimator.isRunning()) { + valueAnimator.cancel(); + } + valueAnimator.start(); + + } + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/account/activity/RegisterStepOneActivity.java b/app/src/main/java/net/oschina/app/improve/account/activity/RegisterStepOneActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..d6e8719a6ff01128b17ddc511ff5dbc030e03670 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/account/activity/RegisterStepOneActivity.java @@ -0,0 +1,528 @@ +package net.oschina.app.improve.account.activity; + +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.os.CountDownTimer; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.animation.DecelerateInterpolator; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.base.AccountBaseActivity; +import net.oschina.app.improve.account.bean.PhoneToken; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.main.synthesize.web.WebActivity; +import net.oschina.app.improve.utils.parser.RichTextParser; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.TDevice; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import butterknife.OnClick; +import cz.msebera.android.httpclient.Header; + +/** + * Created by fei + * on 2016/10/14. + * desc: + */ + +public class RegisterStepOneActivity extends AccountBaseActivity implements View.OnClickListener, View.OnFocusChangeListener, + ViewTreeObserver.OnGlobalLayoutListener { + + @Bind(R.id.ly_retrieve_bar) + LinearLayout mLayBackBar; + + @Bind(R.id.iv_login_logo) + ImageView mIvLogo; + + @Bind(R.id.ll_register_phone) + LinearLayout mLlRegisterPhone; + @Bind(R.id.et_register_username) + EditText mEtRegisterUsername; + @Bind(R.id.iv_register_username_del) + ImageView mIvRegisterDel; + + @Bind(R.id.ll_register_sms_code) + LinearLayout mLlRegisterSmsCode; + @Bind(R.id.et_register_auth_code) + EditText mEtRegisterAuthCode; + + private TextView mTvRegisterSmsCall; + @Bind(R.id.bt_register_submit) + Button mBtRegisterSubmit; + + @Bind(R.id.cb_protocol) + CheckBox mCheckProtocol; + private boolean mMachPhoneNum; + + private CountDownTimer mTimer; + + private int mRequestType = 1;//1. 请求发送验证码 2.请求phoneToken + + + private TextHttpResponseHandler mHandler = new TextHttpResponseHandler() { + + @Override + public void onStart() { + super.onStart(); + showWaitDialog(R.string.progress_submit); + } + + @Override + public void onFinish() { + super.onFinish(); + hideWaitDialog(); + } + + @Override + public void onCancel() { + super.onCancel(); + hideWaitDialog(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (mRequestType == 1) { + if (mTimer != null) { + mTimer.onFinish(); + mTimer.cancel(); + } + } + requestFailureHint(throwable); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + try { + switch (mRequestType) { + //第一步请求发送验证码 + case 1: + Type type = new TypeToken() { + }.getType(); + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + int code = resultBean.getCode(); + switch (code) { + case 1: + //发送验证码成功,请求进入下一步 + //意味着我们可以进行第二次请求了,获取phoneToken + //mRequestType = 2; + AppContext.showToast(R.string.send_sms_code_success_hint); + mEtRegisterAuthCode.setText(null); + break; + case 218: + //手机号已被注册,提示重新输入 + mLlRegisterPhone.setBackgroundResource(R.drawable.bg_login_input_error); + showToastForKeyBord(resultBean.getMessage()); + break; + case 0: + //异常错误,发送验证码失败,回收timer,需重新请求发送验证码 + if (mTimer != null) { + mTimer.onFinish(); + mTimer.cancel(); + } + showToastForKeyBord(resultBean.getMessage()); + break; + default: + break; + } + + break; + //第二步请求进行注册 + case 2: + + Type phoneType = new TypeToken>() { + }.getType(); + + ResultBean phoneTokenResultBean = AppOperator.createGson().fromJson(responseString, phoneType); + int smsCode = phoneTokenResultBean.getCode(); + switch (smsCode) { + case 1://注册成功,进行用户信息填写 + if (phoneTokenResultBean.isSuccess()) { + PhoneToken phoneToken = phoneTokenResultBean.getResult(); + if (phoneToken != null) { + if (mTimer != null) { + mTimer.onFinish(); + mTimer.cancel(); + } + RegisterStepTwoActivity.show(RegisterStepOneActivity.this, phoneToken); + } + } else { + showToastForKeyBord(phoneTokenResultBean.getMessage()); + } + break; + case 215://注册失败,手机验证码错误 + mLlRegisterSmsCode.setBackgroundResource(R.drawable.bg_login_input_error); + showToastForKeyBord(phoneTokenResultBean.getMessage()); + break; + default: + break; + } + + break; + default: + break; + } + + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + + } + }; + private int mLogoHeight; + private int mLogoWidth; + + /** + * show the register activity + * + * @param context context + */ + public static void show(Context context) { + Intent intent = new Intent(context, RegisterStepOneActivity.class); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_main_register_step_one; + } + + @Override + protected void initWidget() { + super.initWidget(); + mTvRegisterSmsCall = (TextView)findViewById(R.id.tv_register_sms_call); + TextView label = (TextView) mLayBackBar.findViewById(R.id.tv_navigation_label); + label.setVisibility(View.INVISIBLE); + + mEtRegisterUsername.addTextChangedListener( + new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + int length = s.length(); + if (length > 0) { + mIvRegisterDel.setVisibility(View.VISIBLE); + } else { + mIvRegisterDel.setVisibility(View.INVISIBLE); + } + } + + @SuppressWarnings("deprecation") + @Override + public void afterTextChanged(Editable s) { + int length = s.length(); + String input = s.toString(); + mMachPhoneNum = RichTextParser.machPhoneNum(input); + + if (mMachPhoneNum) { + String smsCode = mEtRegisterAuthCode.getText().toString().trim(); + + if (!TextUtils.isEmpty(smsCode)) { + mBtRegisterSubmit.setBackgroundResource(R.drawable.bg_login_submit); + mBtRegisterSubmit.setTextColor(getResources().getColor(R.color.white)); + } else { + mBtRegisterSubmit.setBackgroundResource(R.drawable.bg_login_submit_lock); + mBtRegisterSubmit.setTextColor(getResources().getColor(R.color.account_lock_font_color)); + } + } else { + mBtRegisterSubmit.setBackgroundResource(R.drawable.bg_login_submit_lock); + mBtRegisterSubmit.setTextColor(getResources().getColor(R.color.account_lock_font_color)); + } + + if (length > 0 && length < 11) { + mLlRegisterPhone.setBackgroundResource(R.drawable.bg_login_input_error); + mTvRegisterSmsCall.setAlpha(0.4f); + } else if (length == 11) { + if (mMachPhoneNum) { + mLlRegisterPhone.setBackgroundResource(R.drawable.bg_login_input_ok); + if (mTvRegisterSmsCall.getTag() == null) { + mTvRegisterSmsCall.setAlpha(1.0f); + } else { + mTvRegisterSmsCall.setAlpha(0.4f); + } + } else { + mLlRegisterPhone.setBackgroundResource(R.drawable.bg_login_input_error); + showToastForKeyBord(R.string.hint_username_ok); + mTvRegisterSmsCall.setAlpha(0.4f); + } + } else if (length > 11) { + mTvRegisterSmsCall.setAlpha(0.4f); + mLlRegisterPhone.setBackgroundResource(R.drawable.bg_login_input_error); + } else if (length <= 0) { + mTvRegisterSmsCall.setAlpha(0.4f); + mLlRegisterPhone.setBackgroundResource(R.drawable.bg_login_input_ok); + } + + + } + } + + ); + mEtRegisterUsername.setOnFocusChangeListener(this); + mEtRegisterAuthCode.setOnFocusChangeListener(this); + mEtRegisterAuthCode.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @SuppressWarnings("deprecation") + @Override + public void afterTextChanged(Editable s) { + int length = s.length(); + if (length > 0 && mMachPhoneNum) { + mBtRegisterSubmit.setBackgroundResource(R.drawable.bg_login_submit); + mBtRegisterSubmit.setTextColor(getResources().getColor(R.color.white)); + } else { + mBtRegisterSubmit.setBackgroundResource(R.drawable.bg_login_submit_lock); + mBtRegisterSubmit.setTextColor(getResources().getColor(R.color.account_lock_font_color)); + } + mLlRegisterSmsCode.setBackgroundResource(R.drawable.bg_login_input_ok); + } + }); + mCheckProtocol.setChecked(true); + } + + + @Override + protected void initData() { + super.initData();//必须要调用,用来注册本地广播 + } + + @Override + protected void onResume() { + super.onResume(); + mLayBackBar.getViewTreeObserver().addOnGlobalLayoutListener(this); + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void onDestroy() { + super.onDestroy(); + try { + hideKeyBoard(getCurrentFocus().getWindowToken()); + }catch (Exception e){ + e.printStackTrace(); + } + mLayBackBar.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + + @SuppressWarnings("ConstantConditions") + @OnClick({R.id.ib_navigation_back, R.id.iv_register_username_del, R.id.tv_register_sms_call, + R.id.bt_register_submit, R.id.lay_register_one_container, R.id.tv_protocol}) + @Override + public void onClick(View v) { + int id = v.getId(); + switch (id) { + case R.id.ib_navigation_back: + finish(); + break; + case R.id.tv_protocol: + WebActivity.show(this, "https://www.oschina.net/terms"); + break; + case R.id.iv_register_username_del: + mEtRegisterUsername.setText(null); + break; + case R.id.tv_register_sms_call: + requestSmsCode(); + break; + case R.id.bt_register_submit: + if (mCheckProtocol.isChecked()) { + requestRegister(); + } else { + SimplexToast.show(this, "您需要同意开源中国社区用户服务条款方可注册"); + } + break; + case R.id.lay_register_one_container: + try { + hideKeyBoard(getCurrentFocus().getWindowToken()); + }catch (Exception e){ + e.printStackTrace(); + } + break; + default: + break; + } + + } + + private void requestRegister() { + + String smsCode = mEtRegisterAuthCode.getText().toString().trim(); + if (!mMachPhoneNum || TextUtils.isEmpty(smsCode)) { + //showToastForKeyBord(R.string.hint_username_ok); + return; + } + + if (!TDevice.hasInternet()) { + showToastForKeyBord(R.string.tip_network_error); + return; + } + + mRequestType = 2; + String phoneNumber = mEtRegisterUsername.getText().toString().trim(); + OSChinaApi.validateRegisterInfo(phoneNumber, smsCode, mHandler); + } + + private void requestSmsCode() { + if (!mMachPhoneNum) { + //showToastForKeyBord(R.string.hint_username_ok); + return; + } + if (!TDevice.hasInternet()) { + showToastForKeyBord(R.string.tip_network_error); + return; + } + + if (mTvRegisterSmsCall.getTag() == null) { + mRequestType = 1; + mTvRegisterSmsCall.setAlpha(0.6f); + mTvRegisterSmsCall.setTag(true); + mTimer = new CountDownTimer(60 * 1000, 1000) { + + @SuppressLint("DefaultLocale") + @Override + public void onTick(long millisUntilFinished) { + mTvRegisterSmsCall.setText(String.format("%s%s%d%s", + getResources().getString(R.string.register_sms_hint), "(", millisUntilFinished / 1000, ")")); + } + + @Override + public void onFinish() { + mTvRegisterSmsCall.setTag(null); + mTvRegisterSmsCall.setText(getResources().getString(R.string.register_sms_hint)); + mTvRegisterSmsCall.setAlpha(1.0f); + } + }.start(); + String phoneNumber = mEtRegisterUsername.getText().toString().trim(); + OSChinaApi.sendSmsCode(phoneNumber, OSChinaApi.REGISTER_INTENT, mHandler); + } else { + AppContext.showToast(getResources().getString(R.string.register_sms_wait_hint), Toast.LENGTH_SHORT); + } + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + int id = v.getId(); + switch (id) { + case R.id.et_register_username: + if (hasFocus) { + mLlRegisterPhone.setActivated(true); + mLlRegisterSmsCode.setActivated(false); + } + break; + case R.id.et_register_auth_code: + if (hasFocus) { + mLlRegisterSmsCode.setActivated(true); + mLlRegisterPhone.setActivated(false); + } + break; + default: + break; + } + } + + @Override + public void onGlobalLayout() { + + final ImageView ivLogo = this.mIvLogo; + + Rect keypadRect = new Rect(); + + mLayBackBar.getWindowVisibleDisplayFrame(keypadRect); + + int screenHeight = mLayBackBar.getRootView().getHeight(); + + int keypadHeight = screenHeight - keypadRect.bottom; + if (keypadHeight > 0) { + updateKeyBoardActiveStatus(true); + } else { + updateKeyBoardActiveStatus(false); + } + + if (keypadHeight > 0 && ivLogo.getTag() == null) { + final int height = ivLogo.getHeight(); + final int width = ivLogo.getWidth(); + this.mLogoHeight = height; + this.mLogoWidth = width; + ivLogo.setTag(true); + ValueAnimator valueAnimator = ValueAnimator.ofFloat(1, 0); + valueAnimator.setDuration(400).setInterpolator(new DecelerateInterpolator()); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float animatedValue = (float) animation.getAnimatedValue(); + LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) ivLogo.getLayoutParams(); + + layoutParams.height = (int) (height * animatedValue); + layoutParams.width = (int) (width * animatedValue); + ivLogo.requestLayout(); + ivLogo.setAlpha(animatedValue); + } + }); + + if (valueAnimator.isRunning()) { + valueAnimator.cancel(); + } + valueAnimator.start(); + + + } else if (keypadHeight == 0 && ivLogo.getTag() != null) { + final int height = mLogoHeight; + final int width = mLogoWidth; + ivLogo.setTag(null); + ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); + valueAnimator.setDuration(400).setInterpolator(new DecelerateInterpolator()); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float animatedValue = (float) animation.getAnimatedValue(); + LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) ivLogo.getLayoutParams(); + layoutParams.height = (int) (height * animatedValue); + layoutParams.width = (int) (width * animatedValue); + ivLogo.requestLayout(); + ivLogo.setAlpha(animatedValue); + } + }); + + if (valueAnimator.isRunning()) { + valueAnimator.cancel(); + } + valueAnimator.start(); + + } + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/account/activity/RegisterStepTwoActivity.java b/app/src/main/java/net/oschina/app/improve/account/activity/RegisterStepTwoActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..f58e8c3ff04373a2b432cf9d41119984f856c0a4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/account/activity/RegisterStepTwoActivity.java @@ -0,0 +1,418 @@ +package net.oschina.app.improve.account.activity; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.animation.DecelerateInterpolator; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.base.AccountBaseActivity; +import net.oschina.app.improve.account.bean.PhoneToken; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.util.TDevice; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import butterknife.OnClick; +import cz.msebera.android.httpclient.Header; + +/** + * Created by fei + * on 2016/10/14. + * desc: + */ + +public class RegisterStepTwoActivity extends AccountBaseActivity implements View.OnClickListener, View.OnFocusChangeListener, ViewTreeObserver.OnGlobalLayoutListener { + + public static final String PHONE_TOKEN_KEY = "phoneToken"; + + @Bind(R.id.ly_register_bar) + LinearLayout mLlRegisterBar; + + @Bind(R.id.ll_register_two_username) + LinearLayout mLlRegisterTwoUsername; + @Bind(R.id.et_register_username) + EditText mEtRegisterUsername; + @Bind(R.id.iv_register_username_del) + ImageView mIvRegisterUsernameDel; + @Bind(R.id.ll_register_two_pwd) + LinearLayout mLlRegisterTwoPwd; + @Bind(R.id.et_register_pwd_input) + EditText mEtRegisterPwd; + @Bind(R.id.tv_register_man) + TextView mTvRegisterMan; + @Bind(R.id.tv_register_female) + TextView mTvRegisterFemale; + @Bind(R.id.bt_register_submit) + Button mBtRegisterSubmit; + + private PhoneToken mPhoneToken; + + private TextHttpResponseHandler mHandler = new TextHttpResponseHandler() { + + + @Override + public void onStart() { + super.onStart(); + showWaitDialog(R.string.progress_submit); + } + + @Override + public void onFinish() { + super.onFinish(); + hideWaitDialog(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + requestFailureHint(throwable); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + Type type = new TypeToken>() { + }.getType(); + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + + if (resultBean.isSuccess()) { + User user = resultBean.getResult(); + if (AccountHelper.login(user, headers)) { + AppContext.showToast(getResources().getString(R.string.register_success_hint), Toast.LENGTH_SHORT); + sendLocalReceiver(); + finish(); + } else { + showToastForKeyBord("注册异常"); + } + } else { + int code = resultBean.getCode(); + switch (code) { + case 216: + //phoneToken 已经失效 + finish(); + break; + case 217: + mLlRegisterTwoUsername.setBackgroundResource(R.drawable.bg_login_input_error); + break; + case 218: + finish(); + break; + case 219: + mLlRegisterTwoPwd.setBackgroundResource(R.drawable.bg_login_input_error); + break; + default: + break; + } + showToastForKeyBord(resultBean.getMessage()); + } + + } + }; + private int mTopMargin; + + /** + * show register step two activity + * + * @param context context + */ + public static void show(Context context, PhoneToken phoneToken) { + Intent intent = new Intent(context, RegisterStepTwoActivity.class); + intent.putExtra(PHONE_TOKEN_KEY, phoneToken); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_main_register_step_two; + } + + @Override + protected void initWidget() { + super.initWidget(); + + TextView tvLabel = (TextView) mLlRegisterBar.findViewById(R.id.tv_navigation_label); + tvLabel.setText(R.string.login_register_hint); + + mEtRegisterUsername.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @SuppressWarnings("deprecation") + @Override + public void afterTextChanged(Editable s) { + int length = s.length(); + + String smsCode = mEtRegisterPwd.getText().toString().trim(); + + if (!TextUtils.isEmpty(smsCode)) { + mBtRegisterSubmit.setBackgroundResource(R.drawable.bg_login_submit); + mBtRegisterSubmit.setTextColor(getResources().getColor(R.color.white)); + } else { + mBtRegisterSubmit.setBackgroundResource(R.drawable.bg_login_submit_lock); + mBtRegisterSubmit.setTextColor(getResources().getColor(R.color.account_lock_font_color)); + } + + if (length > 0) { + mIvRegisterUsernameDel.setVisibility(View.VISIBLE); + } else { + mIvRegisterUsernameDel.setVisibility(View.INVISIBLE); + } + + if (length > 12) { + showToastForKeyBord(R.string.register_username_error); + mLlRegisterTwoUsername.setBackgroundResource(R.drawable.bg_login_input_error); + } else { + mLlRegisterTwoUsername.setBackgroundResource(R.drawable.bg_login_input_ok); + } + } + }); + mEtRegisterUsername.setOnFocusChangeListener(this); + mEtRegisterPwd.setOnFocusChangeListener(this); + mEtRegisterPwd.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @SuppressWarnings("deprecation") + @Override + public void afterTextChanged(Editable s) { + int length = s.length(); + if (length < 6) { + mLlRegisterTwoPwd.setBackgroundResource(R.drawable.bg_login_input_error); + } else { + mLlRegisterTwoPwd.setBackgroundResource(R.drawable.bg_login_input_ok); + } + String username = mEtRegisterUsername.getText().toString().trim(); + if (!TextUtils.isEmpty(username)) { + mBtRegisterSubmit.setBackgroundResource(R.drawable.bg_login_submit); + mBtRegisterSubmit.setTextColor(getResources().getColor(R.color.white)); + } else { + mBtRegisterSubmit.setBackgroundResource(R.drawable.bg_login_submit_lock); + mBtRegisterSubmit.setTextColor(getResources().getColor(R.color.account_lock_font_color)); + } + } + }); + } + + @Override + protected void initData() { + super.initData();//必须要调用,用来注册本地广播 + Intent intent = getIntent(); + mPhoneToken = (PhoneToken) intent.getSerializableExtra(PHONE_TOKEN_KEY); + } + + @Override + protected void onResume() { + super.onResume(); + mLlRegisterBar.getViewTreeObserver().addOnGlobalLayoutListener(this); + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void onDestroy() { + super.onDestroy(); + hideKeyBoard(getCurrentFocus().getWindowToken()); + mLlRegisterBar.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + + @SuppressWarnings({"deprecation", "ConstantConditions"}) + @OnClick({R.id.ib_navigation_back, R.id.iv_register_username_del, R.id.tv_register_man, + R.id.tv_register_female, R.id.bt_register_submit, R.id.lay_register_two_container}) + @Override + public void onClick(View v) { + int id = v.getId(); + switch (id) { + case R.id.ib_navigation_back: + finish(); + break; + case R.id.iv_register_username_del: + mEtRegisterUsername.setText(null); + break; + case R.id.tv_register_man: + if (mTvRegisterMan.getTag() != null) { + Drawable left = getResources().getDrawable(R.mipmap.btn_gender_male_normal); + mTvRegisterMan.setCompoundDrawablesWithIntrinsicBounds(left, null, null, null); + mTvRegisterMan.setTag(null); + } else { + Drawable left = getResources().getDrawable(R.mipmap.btn_gender_male_actived); + mTvRegisterMan.setCompoundDrawablesWithIntrinsicBounds(left, null, null, null); + mTvRegisterMan.setTag(false); + Drawable female = getResources().getDrawable(R.mipmap.btn_gender_female_normal); + mTvRegisterFemale.setCompoundDrawablesWithIntrinsicBounds(female, null, null, null); + mTvRegisterFemale.setTag(null); + } + + break; + case R.id.tv_register_female: + if (mTvRegisterFemale.getTag() != null) { + Drawable left = getResources().getDrawable(R.mipmap.btn_gender_female_normal); + mTvRegisterFemale.setCompoundDrawablesWithIntrinsicBounds(left, null, null, null); + mTvRegisterFemale.setTag(null); + } else { + Drawable left = getResources().getDrawable(R.mipmap.btn_gender_female_actived); + mTvRegisterFemale.setCompoundDrawablesWithIntrinsicBounds(left, null, null, null); + mTvRegisterFemale.setTag(true); + + Drawable men = getResources().getDrawable(R.mipmap.btn_gender_male_normal); + mTvRegisterMan.setCompoundDrawablesWithIntrinsicBounds(men, null, null, null); + mTvRegisterMan.setTag(null); + } + break; + case R.id.bt_register_submit: + requestRegisterUserInfo(); + break; + case R.id.lay_register_two_container: + hideKeyBoard(getCurrentFocus().getWindowToken()); + break; + default: + break; + } + + } + + private void requestRegisterUserInfo() { + + String username = mEtRegisterUsername.getText().toString().trim(); + String pwd = mEtRegisterPwd.getText().toString().trim(); + + if (TextUtils.isEmpty(username) || TextUtils.isEmpty(pwd)) { + return; + } + + if (!TDevice.hasInternet()) { + showToastForKeyBord(R.string.tip_network_error); + return; + } + + int gender = 0; + + Object isMan = mTvRegisterMan.getTag(); + if (isMan != null) { + gender = 1; + } + + Object isFemale = mTvRegisterFemale.getTag(); + if (isFemale != null) { + gender = 2; + } + + OSChinaApi.register(username, getSha1(pwd), gender, mPhoneToken.getToken(), mHandler); + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + + int id = v.getId(); + switch (id) { + case R.id.et_register_username: + if (hasFocus) { + mLlRegisterTwoUsername.setActivated(true); + mLlRegisterTwoPwd.setActivated(false); + } + break; + case R.id.et_register_pwd_input: + if (hasFocus) { + mLlRegisterTwoPwd.setActivated(true); + mLlRegisterTwoUsername.setActivated(false); + } + break; + default: + break; + } + + } + + @Override + public void onGlobalLayout() { + + final LinearLayout layRegisterTwoUsername = this.mLlRegisterTwoUsername; + Rect keypadRect = new Rect(); + + mLlRegisterBar.getWindowVisibleDisplayFrame(keypadRect); + + int screenHeight = mLlRegisterBar.getRootView().getHeight(); + int keypadHeight = screenHeight - keypadRect.bottom; + + if (keypadHeight > 0) { + updateKeyBoardActiveStatus(true); + } else { + updateKeyBoardActiveStatus(false); + } + + if (keypadHeight > 0 && layRegisterTwoUsername.getTag() == null) { + final LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) layRegisterTwoUsername.getLayoutParams(); + final int topMargin = layoutParams.topMargin; + this.mTopMargin = topMargin; + layRegisterTwoUsername.setTag(true); + ValueAnimator valueAnimator = ValueAnimator.ofFloat(1, 0); + valueAnimator.setDuration(400).setInterpolator(new DecelerateInterpolator()); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float animatedValue = (float) animation.getAnimatedValue(); + layoutParams.topMargin = (int) (topMargin * animatedValue); + layRegisterTwoUsername.requestLayout(); + } + }); + + if (valueAnimator.isRunning()) { + valueAnimator.cancel(); + } + valueAnimator.start(); + + } else if (keypadHeight == 0 && layRegisterTwoUsername.getTag() != null) { + final int topMargin = mTopMargin; + final LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) layRegisterTwoUsername.getLayoutParams(); + layRegisterTwoUsername.setTag(null); + ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); + valueAnimator.setDuration(400).setInterpolator(new DecelerateInterpolator()); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float animatedValue = (float) animation.getAnimatedValue(); + layoutParams.topMargin = (int) (topMargin * animatedValue); + layRegisterTwoUsername.requestLayout(); + } + }); + if (valueAnimator.isRunning()) { + valueAnimator.cancel(); + } + valueAnimator.start(); + + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/account/activity/ResetPwdActivity.java b/app/src/main/java/net/oschina/app/improve/account/activity/ResetPwdActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..b9a42a45f9dfaa065fe81689a48e1676c8f3126a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/account/activity/ResetPwdActivity.java @@ -0,0 +1,300 @@ +package net.oschina.app.improve.account.activity; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.animation.DecelerateInterpolator; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.base.AccountBaseActivity; +import net.oschina.app.improve.account.bean.PhoneToken; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.util.TDevice; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import butterknife.OnClick; +import cz.msebera.android.httpclient.Header; + +/** + * Created by fei + * on 2016/10/14. + * desc: + */ + +public class ResetPwdActivity extends AccountBaseActivity implements View.OnClickListener, View.OnFocusChangeListener, + ViewTreeObserver.OnGlobalLayoutListener { + + @Bind(R.id.ly_reset_bar) + LinearLayout mLlResetBar; + + @Bind(R.id.ll_reset_pwd) + LinearLayout mLlResetPwd; + @Bind(R.id.et_reset_pwd) + EditText mEtResetPwd; + @Bind(R.id.iv_reset_pwd_del) + ImageView mIvResetPwdDel; + @Bind(R.id.bt_reset_submit) + Button mBtResetSubmit; + private PhoneToken mPhoneToken; + private TextHttpResponseHandler mHandler = new TextHttpResponseHandler() { + + @Override + public void onStart() { + super.onStart(); + showWaitDialog(R.string.progress_submit); + } + + @Override + public void onFinish() { + super.onFinish(); + hideWaitDialog(); + } + + @Override + public void onCancel() { + super.onCancel(); + hideWaitDialog(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + requestFailureHint(throwable); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + Type type = new TypeToken() { + }.getType(); + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + int code = resultBean.getCode(); + + switch (code) { + case 1: + AppContext.showToast(getResources().getString(R.string.reset_success_hint), Toast.LENGTH_SHORT); + LoginActivity.show(ResetPwdActivity.this); + finish(); + break; + case 216: + showToastForKeyBord(resultBean.getMessage()); + finish(); + break; + case 219: + mLlResetPwd.setBackgroundResource(R.drawable.bg_login_input_error); + showToastForKeyBord(resultBean.getMessage()); + break; + default: + break; + } + } + }; + private int mTopMargin; + + /** + * show the resetPwdActivity + * + * @param context context + */ + public static void show(Context context, PhoneToken phoneToken) { + Intent intent = new Intent(context, ResetPwdActivity.class); + intent.putExtra(RegisterStepTwoActivity.PHONE_TOKEN_KEY, phoneToken); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_main_reset_pwd; + } + + @Override + protected void initWidget() { + super.initWidget(); + TextView tvLabel = (TextView) mLlResetBar.findViewById(R.id.tv_navigation_label); + tvLabel.setText(R.string.reset_pwd_label); + mEtResetPwd.setOnFocusChangeListener(this); + mEtResetPwd.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @SuppressWarnings("deprecation") + @Override + public void afterTextChanged(Editable s) { + int length = s.length(); + if (length >= 6) { + mIvResetPwdDel.setVisibility(View.VISIBLE); + mLlResetPwd.setBackgroundResource(R.drawable.bg_login_input_ok); + mBtResetSubmit.setBackgroundResource(R.drawable.bg_login_submit); + mBtResetSubmit.setTextColor(getResources().getColor(R.color.white)); + } else { + if (length <= 0) { + mIvResetPwdDel.setVisibility(View.GONE); + mLlResetPwd.setBackgroundResource(R.drawable.bg_login_input_ok); + } else { + mIvResetPwdDel.setVisibility(View.VISIBLE); + mLlResetPwd.setBackgroundResource(R.drawable.bg_login_input_error); + } + mBtResetSubmit.setBackgroundResource(R.drawable.bg_login_submit_lock); + mBtResetSubmit.setTextColor(getResources().getColor(R.color.account_lock_font_color)); + } + + } + }); + } + + @Override + protected void initData() { + super.initData();//必须要调用,用来注册本地广播 + Intent intent = getIntent(); + mPhoneToken = (PhoneToken) intent.getSerializableExtra(RegisterStepTwoActivity.PHONE_TOKEN_KEY); + } + + @Override + protected void onResume() { + super.onResume(); + mLlResetBar.getViewTreeObserver().addOnGlobalLayoutListener(this); + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void onDestroy() { + super.onDestroy(); + hideKeyBoard(getCurrentFocus().getWindowToken()); + mLlResetBar.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + + + @SuppressWarnings("ConstantConditions") + @OnClick({R.id.ib_navigation_back, R.id.iv_reset_pwd_del, R.id.bt_reset_submit, R.id.lay_reset_container}) + @Override + public void onClick(View v) { + int id = v.getId(); + switch (id) { + case R.id.ib_navigation_back: + finish(); + break; + case R.id.iv_reset_pwd_del: + mEtResetPwd.setText(null); + break; + case R.id.bt_reset_submit: + requestResetPwd(); + break; + case R.id.lay_reset_container: + hideKeyBoard(getCurrentFocus().getWindowToken()); + break; + default: + break; + } + + } + + private void requestResetPwd() { + String tempPwd = mEtResetPwd.getText().toString().trim(); + if (TextUtils.isEmpty(tempPwd) || tempPwd.length() < 6) { + //showToastForKeyBord(R.string.reset_pwd_hint); + return; + } + if (!TDevice.hasInternet()) { + showToastForKeyBord(R.string.tip_network_error); + return; + } + + OSChinaApi.resetPwd(getSha1(tempPwd), mPhoneToken.getToken(), mHandler); + } + + + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + mLlResetPwd.setActivated(true); + } + } + + @Override + public void onGlobalLayout() { + + final LinearLayout kayResetPwd = this.mLlResetPwd; + Rect keypadRect = new Rect(); + + mLlResetBar.getWindowVisibleDisplayFrame(keypadRect); + + int screenHeight = mLlResetBar.getRootView().getHeight(); + + int keypadHeight = screenHeight - keypadRect.bottom; + + if (keypadHeight > 0) { + updateKeyBoardActiveStatus(true); + } else { + updateKeyBoardActiveStatus(false); + } + + if (keypadHeight > 0 && kayResetPwd.getTag() == null) { + final LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) kayResetPwd.getLayoutParams(); + final int topMargin = layoutParams.topMargin; + this.mTopMargin = topMargin; + kayResetPwd.setTag(true); + ValueAnimator valueAnimator = ValueAnimator.ofFloat(1, 0); + valueAnimator.setDuration(400).setInterpolator(new DecelerateInterpolator()); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float animatedValue = (float) animation.getAnimatedValue(); + layoutParams.topMargin = (int) (topMargin * animatedValue); + kayResetPwd.requestLayout(); + } + }); + + if (valueAnimator.isRunning()) { + valueAnimator.cancel(); + } + valueAnimator.start(); + + + } else if (keypadHeight == 0 && kayResetPwd.getTag() != null) { + final int topMargin = mTopMargin; + final LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) kayResetPwd.getLayoutParams(); + kayResetPwd.setTag(null); + ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); + valueAnimator.setDuration(400).setInterpolator(new DecelerateInterpolator()); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float animatedValue = (float) animation.getAnimatedValue(); + layoutParams.topMargin = (int) (topMargin * animatedValue); + kayResetPwd.requestLayout(); + } + }); + if (valueAnimator.isRunning()) { + valueAnimator.cancel(); + } + valueAnimator.start(); + + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/account/activity/RetrieveActivity.java b/app/src/main/java/net/oschina/app/improve/account/activity/RetrieveActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..d50e070ce55bb1ef9236a767b759ac71fc13d6d9 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/account/activity/RetrieveActivity.java @@ -0,0 +1,508 @@ +package net.oschina.app.improve.account.activity; + +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.net.Uri; +import android.os.CountDownTimer; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.animation.DecelerateInterpolator; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.base.AccountBaseActivity; +import net.oschina.app.improve.account.bean.PhoneToken; +import net.oschina.app.improve.account.constants.UserConstants; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.utils.parser.RichTextParser; +import net.oschina.app.util.TDevice; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import butterknife.OnClick; +import cz.msebera.android.httpclient.Header; + +/** + * Created by fei + * on 2016/10/14. + * desc: + */ + +public class RetrieveActivity extends AccountBaseActivity implements View.OnClickListener, View.OnFocusChangeListener, + ViewTreeObserver.OnGlobalLayoutListener { + + @Bind(R.id.ly_retrieve_bar) + LinearLayout mLlRetrieveBar; + + @Bind(R.id.ll_retrieve_tel) + LinearLayout mLlRetrieveTel; + @Bind(R.id.et_retrieve_tel) + EditText mEtRetrieveTel; + @Bind(R.id.iv_retrieve_tel_del) + ImageView mIvRetrieveTelDel; + + @Bind(R.id.ll_retrieve_code) + LinearLayout mLlRetrieveCode; + @Bind(R.id.et_retrieve_code_input) + EditText mEtRetrieveCodeInput; + @Bind(R.id.retrieve_sms_call) + TextView mTvRetrieveSmsCall; + + @Bind(R.id.bt_retrieve_submit) + Button mBtRetrieveSubmit; + @Bind(R.id.tv_retrieve_label) + TextView mTvRetrieveLabel; + private boolean mMachPhoneNum; + + private CountDownTimer mTimer; + + private int mRequestType; + private TextHttpResponseHandler mHandler = new TextHttpResponseHandler() { + + @Override + public void onStart() { + super.onStart(); + showWaitDialog(R.string.progress_submit); + } + + @Override + public void onFinish() { + super.onFinish(); + hideWaitDialog(); + } + + @Override + public void onCancel() { + super.onCancel(); + hideWaitDialog(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + //请求失败,比如服务器连接超时,回收timer,需重新请求发送验证码 + if (mRequestType == 1) { + if (mTimer != null) { + mTimer.onFinish(); + mTimer.cancel(); + } + } + requestFailureHint(throwable); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + try { + switch (mRequestType) { + //第一步请求发送验证码 + case 1: + Type type = new TypeToken() { + }.getType(); + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + int code = resultBean.getCode(); + switch (code) { + case 1: + //发送验证码成功,请求进入下一步 + //mRequestType = 2; + mEtRetrieveCodeInput.setText(null); + AppContext.showToast(R.string.send_sms_code_success_hint, Toast.LENGTH_SHORT); + break; + case 218: + //手机号已被注册,提示重新输入 + mLlRetrieveTel.setBackgroundResource(R.drawable.bg_login_input_error); + showToastForKeyBord(resultBean.getMessage()); + break; + case 0: + //异常错误,发送验证码失败,回收timer,需重新请求发送验证码 + if (mTimer != null) { + mTimer.onFinish(); + mTimer.cancel(); + } + showToastForKeyBord(resultBean.getMessage()); + break; + default: + break; + } + + break; + //第二步请求进行重置密码 + case 2: + + Type phoneType = new TypeToken>() { + }.getType(); + + ResultBean phoneTokenResultBean = AppOperator.createGson().fromJson(responseString, phoneType); + + int smsCode = phoneTokenResultBean.getCode(); + switch (smsCode) { + case 1: + if (phoneTokenResultBean.isSuccess()) { + PhoneToken phoneToken = phoneTokenResultBean.getResult(); + if (phoneToken != null) { + if (mTimer != null) { + mTimer.onFinish(); + mTimer.cancel(); + } + ResetPwdActivity.show(RetrieveActivity.this, phoneToken); + } + } else { + showToastForKeyBord(phoneTokenResultBean.getMessage()); + } + break; + case 215: + mLlRetrieveCode.setBackgroundResource(R.drawable.bg_login_input_error); + showToastForKeyBord(phoneTokenResultBean.getMessage()); + break; + default: + break; + } + break; + default: + break; + } + + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + + } + }; + private int mTopMargin; + + /** + * show the retrieve activity + * + * @param context context + */ + public static void show(Context context) { + Intent intent = new Intent(context, RetrieveActivity.class); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_main_retrieve_pwd; + } + + @Override + protected void initWidget() { + super.initWidget(); + + TextView tvLabel = (TextView) mLlRetrieveBar.findViewById(R.id.tv_navigation_label); + tvLabel.setText(R.string.retrieve_pwd_label); + mEtRetrieveTel.setOnFocusChangeListener(this); + mEtRetrieveTel.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + int length = s.length(); + if (length > 0) { + mIvRetrieveTelDel.setVisibility(View.VISIBLE); + } else { + mIvRetrieveTelDel.setVisibility(View.INVISIBLE); + } + + } + + @SuppressWarnings("deprecation") + @Override + public void afterTextChanged(Editable s) { + int length = s.length(); + String input = s.toString(); + mMachPhoneNum = RichTextParser.machPhoneNum(input); + + //对提交控件的状态判定 + if (mMachPhoneNum) { + String smsCode = mEtRetrieveCodeInput.getText().toString().trim(); + + if (!TextUtils.isEmpty(smsCode)) { + mBtRetrieveSubmit.setBackgroundResource(R.drawable.bg_login_submit); + mBtRetrieveSubmit.setTextColor(getResources().getColor(R.color.white)); + } else { + mBtRetrieveSubmit.setBackgroundResource(R.drawable.bg_login_submit_lock); + mBtRetrieveSubmit.setTextColor(getResources().getColor(R.color.account_lock_font_color)); + } + } else { + mBtRetrieveSubmit.setBackgroundResource(R.drawable.bg_login_submit_lock); + mBtRetrieveSubmit.setTextColor(getResources().getColor(R.color.account_lock_font_color)); + } + + if (length > 0 && length < 11) { + mLlRetrieveTel.setBackgroundResource(R.drawable.bg_login_input_error); + mTvRetrieveSmsCall.setAlpha(0.4f); + } else if (length == 11) { + if (mMachPhoneNum) { + mLlRetrieveTel.setBackgroundResource(R.drawable.bg_login_input_ok); + if (mTvRetrieveSmsCall.getTag() == null) { + mTvRetrieveSmsCall.setAlpha(1.0f); + } else { + mTvRetrieveSmsCall.setAlpha(0.4f); + } + } else { + mLlRetrieveTel.setBackgroundResource(R.drawable.bg_login_input_error); + showToastForKeyBord(R.string.hint_username_ok); + mTvRetrieveSmsCall.setAlpha(0.4f); + } + } else if (length > 11) { + mTvRetrieveSmsCall.setAlpha(0.4f); + mLlRetrieveTel.setBackgroundResource(R.drawable.bg_login_input_error); + } else if (length <= 0) { + mTvRetrieveSmsCall.setAlpha(0.4f); + mLlRetrieveTel.setBackgroundResource(R.drawable.bg_login_input_ok); + } + + } + }); + mEtRetrieveCodeInput.setOnFocusChangeListener(this); + mEtRetrieveCodeInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @SuppressWarnings("deprecation") + @Override + public void afterTextChanged(Editable s) { + int length = s.length(); + if (length > 0 && mMachPhoneNum) { + mBtRetrieveSubmit.setBackgroundResource(R.drawable.bg_login_submit); + mBtRetrieveSubmit.setTextColor(getResources().getColor(R.color.white)); + } else { + mBtRetrieveSubmit.setBackgroundResource(R.drawable.bg_login_submit_lock); + mBtRetrieveSubmit.setTextColor(getResources().getColor(R.color.account_lock_font_color)); + } + mLlRetrieveCode.setBackgroundResource(R.drawable.bg_login_input_ok); + } + }); + } + + @Override + protected void initData() { + super.initData();//必须要调用,用来注册本地广播 + } + + @Override + protected void onResume() { + super.onResume(); + mLlRetrieveBar.getViewTreeObserver().addOnGlobalLayoutListener(this); + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void onDestroy() { + super.onDestroy(); + hideKeyBoard(getCurrentFocus().getWindowToken()); + mLlRetrieveBar.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + + @SuppressWarnings("ConstantConditions") + @OnClick({R.id.ib_navigation_back, R.id.iv_retrieve_tel_del, R.id.retrieve_sms_call, + R.id.bt_retrieve_submit, R.id.tv_retrieve_label, R.id.lay_retrieve_container}) + @Override + public void onClick(View v) { + int id = v.getId(); + switch (id) { + case R.id.ib_navigation_back: + finish(); + break; + case R.id.iv_retrieve_tel_del: + mEtRetrieveTel.setText(null); + break; + case R.id.retrieve_sms_call: + //获取验证码 + requestSmsCode(); + + break; + case R.id.bt_retrieve_submit: + //根据验证码获取phoneToken + requestRetrievePwd(); + break; + case R.id.tv_retrieve_label: + + //打开web进入邮箱找回密码 + + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + // intent.setAction(Intent.CATEGORY_BROWSABLE); + Uri content_url = Uri.parse(UserConstants.RETRIEVE_PWD_URL); + intent.setData(content_url); + startActivity(intent); + break; + case R.id.lay_retrieve_container: + try { + hideKeyBoard(getCurrentFocus().getWindowToken()); + }catch (Exception e){ + e.printStackTrace(); + } + break; + default: + break; + } + + } + + private void requestRetrievePwd() { + + String smsCode = mEtRetrieveCodeInput.getText().toString().trim(); + if (!mMachPhoneNum || TextUtils.isEmpty(smsCode)) { + // showToastForKeyBord(R.string.hint_username_ok); + return; + } + + if (!TDevice.hasInternet()) { + showToastForKeyBord(R.string.tip_network_error); + return; + } + mRequestType = 2; + String phoneNumber = mEtRetrieveTel.getText().toString().trim(); + OSChinaApi.validateRegisterInfo(phoneNumber, smsCode, mHandler); + } + + private void requestSmsCode() { + if (!mMachPhoneNum) { + //showToastForKeyBord(R.string.hint_username_ok); + return; + } + + if (!TDevice.hasInternet()) { + showToastForKeyBord(R.string.tip_network_error); + return; + } + + if (mTvRetrieveSmsCall.getTag() == null) { + mRequestType = 1; + mTvRetrieveSmsCall.setAlpha(0.6f); + mTvRetrieveSmsCall.setTag(true); + mTimer = new CountDownTimer(60 * 1000, 1000) { + + @SuppressLint("DefaultLocale") + @Override + public void onTick(long millisUntilFinished) { + mTvRetrieveSmsCall.setText(String.format("%s%s%d%s", + getResources().getString(R.string.register_sms_hint), "(", millisUntilFinished / 1000, ")")); + } + + @Override + public void onFinish() { + mTvRetrieveSmsCall.setTag(null); + mTvRetrieveSmsCall.setText(getResources().getString(R.string.register_sms_hint)); + mTvRetrieveSmsCall.setAlpha(1.0f); + } + }.start(); + String phoneNumber = mEtRetrieveTel.getText().toString().trim(); + OSChinaApi.sendSmsCode(phoneNumber, OSChinaApi.RESET_PWD_INTENT, mHandler); + } else { + AppContext.showToast(getResources().getString(R.string.register_sms_wait_hint), Toast.LENGTH_SHORT); + } + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + int id = v.getId(); + switch (id) { + case R.id.et_retrieve_tel: + if (hasFocus) { + mLlRetrieveTel.setActivated(true); + mLlRetrieveCode.setActivated(false); + } + break; + case R.id.et_retrieve_code_input: + if (hasFocus) { + mLlRetrieveCode.setActivated(true); + mLlRetrieveTel.setActivated(false); + } + break; + default: + break; + } + } + + @Override + public void onGlobalLayout() { + + final LinearLayout layRetrieveTel = this.mLlRetrieveTel; + Rect KeypadRect = new Rect(); + + mLlRetrieveBar.getWindowVisibleDisplayFrame(KeypadRect); + + int screenHeight = mLlRetrieveBar.getRootView().getHeight(); + + int keypadHeight = screenHeight - KeypadRect.bottom; + + if (keypadHeight > 0) { + updateKeyBoardActiveStatus(true); + } else { + updateKeyBoardActiveStatus(false); + } + + if (keypadHeight > 0 && layRetrieveTel.getTag() == null) { + final LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) layRetrieveTel.getLayoutParams(); + final int topMargin = layoutParams.topMargin; + this.mTopMargin = topMargin; + layRetrieveTel.setTag(true); + ValueAnimator valueAnimator = ValueAnimator.ofFloat(1, 0); + valueAnimator.setDuration(400).setInterpolator(new DecelerateInterpolator()); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float animatedValue = (float) animation.getAnimatedValue(); + layoutParams.topMargin = (int) (topMargin * animatedValue); + layRetrieveTel.requestLayout(); + } + }); + + if (valueAnimator.isRunning()) { + valueAnimator.cancel(); + } + valueAnimator.start(); + + + } else if (keypadHeight == 0 && layRetrieveTel.getTag() != null) { + final int topMargin = mTopMargin; + final LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) layRetrieveTel.getLayoutParams(); + layRetrieveTel.setTag(null); + ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); + valueAnimator.setDuration(400).setInterpolator(new DecelerateInterpolator()); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float animatedValue = (float) animation.getAnimatedValue(); + layoutParams.topMargin = (int) (topMargin * animatedValue); + layRetrieveTel.requestLayout(); + } + }); + if (valueAnimator.isRunning()) { + valueAnimator.cancel(); + } + valueAnimator.start(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/account/base/AccountBaseActivity.java b/app/src/main/java/net/oschina/app/improve/account/base/AccountBaseActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..7240df7a317463e7ba5eb43a38c2bb79c0539f69 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/account/base/AccountBaseActivity.java @@ -0,0 +1,300 @@ +package net.oschina.app.improve.account.base; + +import android.annotation.SuppressLint; +import android.app.ProgressDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.IBinder; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.v4.content.LocalBroadcastManager; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.TextView; +import android.widget.Toast; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.utils.DialogHelper; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Created by fei + * on 2016/10/31. + * desc: + */ + +public class AccountBaseActivity extends BaseActivity { + + private ProgressDialog mDialog; + public static final String ACTION_ACCOUNT_FINISH_ALL = "app.oschina.net.action.finish.all"; + protected LocalBroadcastManager mManager; + private BroadcastReceiver mReceiver; + protected InputMethodManager mInputMethodManager; + protected Toast mToast; + private boolean mKeyBoardIsActive; + + @Override + protected int getContentView() { + return 0; + } + + @Override + protected void initData() { + super.initData(); + registerLocalReceiver(); + mInputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + finish(); + } + + @Override + protected void onStop() { + super.onStop(); + hideWaitDialog(); + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void onDestroy() { + super.onDestroy(); + try { + hideKeyBoard(getCurrentFocus().getWindowToken()); + }catch (Exception e){ + e.printStackTrace(); + } + if (mManager != null) { + if (mReceiver != null) + mManager.unregisterReceiver(mReceiver); + } + } + + /** + * showToast + * + * @param text text + */ + @SuppressLint("InflateParams") + private void showToast(String text) { + Toast toast = this.mToast; + if (toast == null) { + toast = initToast(); + } + View rootView = LayoutInflater.from(this).inflate(R.layout.view_toast, null, false); + TextView textView = (TextView) rootView.findViewById(R.id.title_tv); + textView.setText(text); + toast.setView(rootView); + initToastGravity(toast); + toast.show(); + } + + /** + * showToast + * + * @param id id + */ + @SuppressLint("InflateParams") + private void showToast(@StringRes int id) { + Toast toast = this.mToast; + if (toast == null) { + toast = initToast(); + } + View rootView = LayoutInflater.from(this).inflate(R.layout.view_toast, null, false); + TextView textView = (TextView) rootView.findViewById(R.id.title_tv); + textView.setText(id); + toast.setView(rootView); + initToastGravity(toast); + toast.show(); + } + + @NonNull + private Toast initToast() { + Toast toast; + toast = new Toast(this); + toast.setDuration(Toast.LENGTH_SHORT); + this.mToast = toast; + return toast; + } + + private void initToastGravity(Toast toast) { + boolean isCenter = this.mKeyBoardIsActive; + if (isCenter) { + toast.setGravity(Gravity.CENTER_HORIZONTAL, 0, 0); + } else { + toast.setGravity(Gravity.BOTTOM, 0, getResources().getDimensionPixelSize(R.dimen.toast_y_offset)); + } + } + + /** + * update keyBord active status + * + * @param isActive isActive + */ + protected void updateKeyBoardActiveStatus(boolean isActive) { + this.mKeyBoardIsActive = isActive; + } + + /** + * cancelToast + */ + protected void cancelToast() { + if (mToast != null) { + mToast.cancel(); + } + } + + protected boolean sendLocalReceiver() { + if (mManager != null) { + Intent intent = new Intent(); + intent.setAction(ACTION_ACCOUNT_FINISH_ALL); + return mManager.sendBroadcast(intent); + } + + return false; + } + + /** + * register localReceiver + */ + private void registerLocalReceiver() { + if (mManager == null) + mManager = LocalBroadcastManager.getInstance(this); + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_ACCOUNT_FINISH_ALL); + if (mReceiver == null) + mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (ACTION_ACCOUNT_FINISH_ALL.equals(action)) { + finish(); + } + } + }; + mManager.registerReceiver(mReceiver, filter); + } + + /** + * show WaitDialog + * + * @return progressDialog + */ + protected ProgressDialog showWaitDialog(@StringRes int messageId) { + if (mDialog == null) { + if (messageId <= 0) { + mDialog = DialogHelper.getProgressDialog(this, true); + } else { + String message = getResources().getString(messageId); + mDialog = DialogHelper.getProgressDialog(this, message, true); + } + } + mDialog.show(); + + return mDialog; + } + + /** + * show FocusWaitDialog + * + * @return progressDialog + */ + protected ProgressDialog showFocusWaitDialog() { + + String message = getResources().getString(R.string.progress_submit); + if (mDialog == null) { + mDialog = DialogHelper.getProgressDialog(this, message, false);//DialogHelp.getWaitDialog(this, message); + } + mDialog.show(); + + return mDialog; + } + + /** + * hide waitDialog + */ + protected void hideWaitDialog() { + ProgressDialog dialog = mDialog; + if (dialog != null) { + mDialog = null; + try { + dialog.cancel(); + // dialog.dismiss(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + protected void showToastForKeyBord(@StringRes int id) { + showToast(id); + } + + protected void showToastForKeyBord(String message) { + showToast(message); + } + + protected void hideKeyBoard(IBinder windowToken) { + if(windowToken == null){ + return; + } + InputMethodManager inputMethodManager = this.mInputMethodManager; + if (inputMethodManager == null) return; + boolean active = inputMethodManager.isActive(); + if (active) { + inputMethodManager.hideSoftInputFromWindow(windowToken, 0); + } + } + + /** + * request network error + * + * @param throwable throwable + */ + protected void requestFailureHint(Throwable throwable) { + if (throwable != null) { + throwable.printStackTrace(); + } + showToastForKeyBord(R.string.request_error_hint); + } + + /** + * sha-1 to hex + * + * @param tempPwd tempPwd + * @return sha-1 pwd + */ + @NonNull + protected String getSha1(String tempPwd) { + try { + MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); + messageDigest.update(tempPwd.getBytes("utf-8")); + byte[] bytes = messageDigest.digest(); + + StringBuilder tempHex = new StringBuilder(); + // 字节数组转换为 十六进制数 + for (byte aByte : bytes) { + String shaHex = Integer.toHexString(aByte & 0xff); + if (shaHex.length() < 2) { + tempHex.append(0); + } + tempHex.append(shaHex); + } + return tempHex.toString(); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + e.printStackTrace(); + } + + return tempPwd; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/account/bean/PhoneToken.java b/app/src/main/java/net/oschina/app/improve/account/bean/PhoneToken.java new file mode 100644 index 0000000000000000000000000000000000000000..d853ffe529d7b9a280d2c309157387ba777e2a47 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/account/bean/PhoneToken.java @@ -0,0 +1,49 @@ +package net.oschina.app.improve.account.bean; + +import java.io.Serializable; + +/** + * Created by fei + * on 2016/10/26. + * desc: + */ + +public class PhoneToken implements Serializable { + + private String phone; + private String token; + private String expireDate; + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getExpireDate() { + return expireDate; + } + + public void setExpireDate(String expireDate) { + this.expireDate = expireDate; + } + + @Override + public String toString() { + return "PhoneToken{" + + "phone='" + phone + '\'' + + ", token='" + token + '\'' + + ", expireDate='" + expireDate + '\'' + + '}'; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/account/constants/UserConstants.java b/app/src/main/java/net/oschina/app/improve/account/constants/UserConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..26ab27d58e100a1e3772d6a6390f1fb3f223fb6a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/account/constants/UserConstants.java @@ -0,0 +1,14 @@ +package net.oschina.app.improve.account.constants; + +/** + * Created by fei + * on 2016/10/24. + * desc: + */ + +public interface UserConstants { + + String HOLD_ACCOUNT = "hold_account"; + String RETRIEVE_PWD_URL = "https://www.oschina.net/home/reset-pwd"; + +} diff --git a/app/src/main/java/net/oschina/app/improve/app/AppOperator.java b/app/src/main/java/net/oschina/app/improve/app/AppOperator.java new file mode 100644 index 0000000000000000000000000000000000000000..3000520d742426472fe91f3bcacb059411ae9a28 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/app/AppOperator.java @@ -0,0 +1,99 @@ +package net.oschina.app.improve.app; + +import android.os.Handler; +import android.os.Looper; + +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.load.model.LazyHeaders; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializer; + +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.gson.DoubleJsonDeserializer; +import net.oschina.app.improve.app.gson.FloatJsonDeserializer; +import net.oschina.app.improve.app.gson.ImageJsonDeserializer; +import net.oschina.app.improve.app.gson.IntegerJsonDeserializer; +import net.oschina.app.improve.app.gson.StringJsonDeserializer; +import net.oschina.app.improve.bean.Tweet; + +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created by JuQiu + * on 16/6/24. + */ +public final class AppOperator { + private static Handler mHandler; + private static ExecutorService EXECUTORS_INSTANCE; + private static Gson GSON_INSTANCE; + + public static Executor getExecutor() { + if (EXECUTORS_INSTANCE == null) { + synchronized (AppOperator.class) { + if (EXECUTORS_INSTANCE == null) { + EXECUTORS_INSTANCE = Executors.newFixedThreadPool(6); + } + } + } + return EXECUTORS_INSTANCE; + } + + + public static void runOnMainThread(Runnable runnable) { + if (mHandler == null) { + mHandler = new Handler(Looper.getMainLooper()); + } + mHandler.post(runnable); + } + + public static void runOnThread(Runnable runnable) { + getExecutor().execute(runnable); + } + + public static GlideUrl getGlideUrlByUser(String url) { + if (AccountHelper.isLogin()) { + return new GlideUrl(url, + new LazyHeaders + .Builder() + .addHeader("Cookie", AccountHelper.getCookie()) + .build()); + } else { + return new GlideUrl(url); + } + } + + public static Gson createGson() { + GsonBuilder gsonBuilder = new GsonBuilder(); + //gsonBuilder.setExclusionStrategies(new SpecificClassExclusionStrategy(null, Model.class)); + gsonBuilder.setDateFormat("yyyy-MM-dd HH:mm:ss"); + + JsonDeserializer deserializer = new IntegerJsonDeserializer(); + gsonBuilder.registerTypeAdapter(int.class, deserializer); + gsonBuilder.registerTypeAdapter(Integer.class, deserializer); + + deserializer = new FloatJsonDeserializer(); + gsonBuilder.registerTypeAdapter(float.class, deserializer); + gsonBuilder.registerTypeAdapter(Float.class, deserializer); + + deserializer = new DoubleJsonDeserializer(); + gsonBuilder.registerTypeAdapter(double.class, deserializer); + gsonBuilder.registerTypeAdapter(Double.class, deserializer); + + deserializer = new StringJsonDeserializer(); + gsonBuilder.registerTypeAdapter(String.class, deserializer); + + gsonBuilder.registerTypeAdapter(Tweet.Image.class, new ImageJsonDeserializer()); + + return gsonBuilder.create(); + } + + public synchronized static Gson getGson() { + if (GSON_INSTANCE == null) + GSON_INSTANCE = createGson(); + return GSON_INSTANCE; + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/app/ParentLinkedHolder.java b/app/src/main/java/net/oschina/app/improve/app/ParentLinkedHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..21531f78855d14a51cf9d7d65c905b6770be41a7 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/app/ParentLinkedHolder.java @@ -0,0 +1,44 @@ +package net.oschina.app.improve.app; + +import android.support.annotation.NonNull; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ + +public class ParentLinkedHolder { + public T item; + private ParentLinkedHolder parentLinkedHolder; + + public ParentLinkedHolder(@NonNull T item) { + this.item = item; + } + + public T get() { + return item; + } + + public boolean hasParent() { + return parentLinkedHolder != null; + } + + public ParentLinkedHolder addParent(ParentLinkedHolder holder) { + parentLinkedHolder = holder; + return this; + } + + public ParentLinkedHolder putParent() { + ParentLinkedHolder holder = parentLinkedHolder; + parentLinkedHolder = null; + return holder; + } + + @Override + public String toString() { + return "ParentLinkedHolder{" + + "item=" + item + + ", parentLinkedHolder=" + parentLinkedHolder + + '}'; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/app/gson/DoubleJsonDeserializer.java b/app/src/main/java/net/oschina/app/improve/app/gson/DoubleJsonDeserializer.java new file mode 100644 index 0000000000000000000000000000000000000000..3115b3059329fe4c96549f537153869ea04903cc --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/app/gson/DoubleJsonDeserializer.java @@ -0,0 +1,26 @@ +package net.oschina.app.improve.app.gson; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +import net.oschina.app.util.TLog; + +import java.lang.reflect.Type; + +/** + * Created by qiujuer + * on 2016/11/22. + */ +public class DoubleJsonDeserializer implements JsonDeserializer { + @Override + public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + try { + return json.getAsDouble(); + } catch (Exception e) { + TLog.log("DoubleJsonDeserializer-deserialize-error:" + (json != null ? json.toString() : "")); + return 0D; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/app/gson/FloatJsonDeserializer.java b/app/src/main/java/net/oschina/app/improve/app/gson/FloatJsonDeserializer.java new file mode 100644 index 0000000000000000000000000000000000000000..7d6c1f4ab2c37aeca61b940494e8ebcf6f97aca4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/app/gson/FloatJsonDeserializer.java @@ -0,0 +1,26 @@ +package net.oschina.app.improve.app.gson; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +import net.oschina.app.util.TLog; + +import java.lang.reflect.Type; + +/** + * Created by qiujuer + * on 2016/11/22. + */ +public class FloatJsonDeserializer implements JsonDeserializer { + @Override + public Float deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + try { + return json.getAsFloat(); + } catch (Exception e) { + TLog.log("FloatJsonDeserializer-deserialize-error:" + (json != null ? json.toString() : "")); + return 0F; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/app/gson/ImageJsonDeserializer.java b/app/src/main/java/net/oschina/app/improve/app/gson/ImageJsonDeserializer.java new file mode 100644 index 0000000000000000000000000000000000000000..3ff471c39bee21486f7a3caa29532a8c1da0ab61 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/app/gson/ImageJsonDeserializer.java @@ -0,0 +1,41 @@ +package net.oschina.app.improve.app.gson; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.util.TLog; + +import java.lang.reflect.Type; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ + +public class ImageJsonDeserializer implements JsonDeserializer { + @Override + public Tweet.Image deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + try { + if (json.isJsonObject()) { + Tweet.Image image = new Tweet.Image(); + // The whole object is available + final JsonObject jsonObject = json.getAsJsonObject(); + image.setThumb(context.deserialize(jsonObject.get("thumb"), String.class)); + image.setHref(context.deserialize(jsonObject.get("href"), String.class)); + image.setH(context.deserialize(jsonObject.get("h"), int.class)); + image.setW(context.deserialize(jsonObject.get("w"), int.class)); + if (Tweet.Image.check(image)) + return image; + else + return null; + } + } catch (Exception e) { + TLog.error("ImageJsonDeserializer-deserialize-error:" + (json != null ? json.toString() : "")); + } + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/app/gson/IntegerJsonDeserializer.java b/app/src/main/java/net/oschina/app/improve/app/gson/IntegerJsonDeserializer.java new file mode 100644 index 0000000000000000000000000000000000000000..e5cf6d7d823086fa3274eef4c5a222f8fa56864f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/app/gson/IntegerJsonDeserializer.java @@ -0,0 +1,26 @@ +package net.oschina.app.improve.app.gson; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +import net.oschina.app.util.TLog; + +import java.lang.reflect.Type; + +/** + * Created by qiujuer + * on 2016/11/22. + */ +public class IntegerJsonDeserializer implements JsonDeserializer { + @Override + public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + try { + return json.getAsInt(); + } catch (Exception e) { + TLog.log("IntegerJsonDeserializer-deserialize-error:" + (json != null ? json.toString() : "")); + return 0; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/app/gson/StringJsonDeserializer.java b/app/src/main/java/net/oschina/app/improve/app/gson/StringJsonDeserializer.java new file mode 100644 index 0000000000000000000000000000000000000000..1d55ea6a1281009a70c4b247990d2ced046ca222 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/app/gson/StringJsonDeserializer.java @@ -0,0 +1,26 @@ +package net.oschina.app.improve.app.gson; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +import net.oschina.app.util.TLog; + +import java.lang.reflect.Type; + +/** + * Created by qiujuer + * on 2016/11/22. + */ +public class StringJsonDeserializer implements JsonDeserializer { + @Override + public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + try { + return json.getAsString(); + } catch (Exception e) { + TLog.log("StringJsonDeserializer-deserialize-error:" + (json != null ? json.toString() : "")); + return null; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/BaseListPresenter.java b/app/src/main/java/net/oschina/app/improve/base/BaseListPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..07e9fb9777afa92846cbaf28dfe4d274c07471d2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/BaseListPresenter.java @@ -0,0 +1,12 @@ +package net.oschina.app.improve.base; + +/** + * Created by haibin + * on 2016/11/30. + */ + +public interface BaseListPresenter extends BasePresenter { + void onRefreshing(); + + void onLoadMore(); +} diff --git a/app/src/main/java/net/oschina/app/improve/base/BaseListView.java b/app/src/main/java/net/oschina/app/improve/base/BaseListView.java new file mode 100644 index 0000000000000000000000000000000000000000..01a4d8a823dd1be226e599bed404f3f4a1764532 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/BaseListView.java @@ -0,0 +1,30 @@ +package net.oschina.app.improve.base; + +import java.util.List; + +/** + * Created by haibin + * on 2016/11/30. + */ + +public interface BaseListView extends BaseView { + /** + * 刷新成功 + */ + void onRefreshSuccess(List data); + + /** + * 加载成功 + */ + void onLoadMoreSuccess(List data); + + /** + * 没有更多数据 + */ + void showNotMore(); + + /** + * 加载完成 + */ + void onComplete(); +} diff --git a/app/src/main/java/net/oschina/app/improve/base/BasePresenter.java b/app/src/main/java/net/oschina/app/improve/base/BasePresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..c21c3e017271044e701cd4dd08a290f074e961c2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/BasePresenter.java @@ -0,0 +1,10 @@ +package net.oschina.app.improve.base; + +/** + * Created by haibin + * on 2016/11/30. + */ + +public interface BasePresenter { + +} diff --git a/app/src/main/java/net/oschina/app/improve/base/BaseRecyclerFragment.java b/app/src/main/java/net/oschina/app/improve/base/BaseRecyclerFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..0fe07aa23e21b5f7554df92da13a3a1955a44727 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/BaseRecyclerFragment.java @@ -0,0 +1,146 @@ +package net.oschina.app.improve.base; + +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.widget.RecyclerRefreshLayout; + +import java.util.Date; +import java.util.List; + +/** + * MVP刷新列表基类,再无逻辑判断,清晰自然 + * Created by haibin + * on 2016/12/27. + */ + +public abstract class BaseRecyclerFragment extends BaseFragment + implements BaseListView, + BaseRecyclerAdapter.OnItemClickListener, + RecyclerRefreshLayout.SuperRefreshLayoutListener, + BaseGeneralRecyclerAdapter.Callback { + protected RecyclerRefreshLayout mRefreshLayout; + protected RecyclerView mRecyclerView; + protected BaseRecyclerAdapter mAdapter; + protected Presenter mPresenter; + + @Override + protected int getLayoutId() { + return R.layout.fragment_base_recycler; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mRefreshLayout = (RecyclerRefreshLayout) mRoot.findViewById(R.id.refreshLayout); + mRefreshLayout.setSuperRefreshLayoutListener(this); + mRecyclerView = (RecyclerView) mRoot.findViewById(R.id.recyclerView); + initHeader(); + mAdapter = getAdapter(); + mRecyclerView.setLayoutManager(getLayoutManager()); + mRecyclerView.setAdapter(mAdapter); + mAdapter.setOnItemClickListener(this); + mRefreshLayout.setColorSchemeResources( + R.color.swiperefresh_color1, R.color.swiperefresh_color2, + R.color.swiperefresh_color3, R.color.swiperefresh_color4); + hokeSetHeaderView(); + } + + protected void initHeader() { + + } + + + protected void hokeSetHeaderView() { + + } + + @Override + protected void initData() { + super.initData(); + mRefreshLayout.post(new Runnable() { + @Override + public void run() { + mRefreshLayout.setRefreshing(true); + if (mPresenter == null) + return; + mPresenter.onRefreshing(); + } + }); + } + + @Override + public void onItemClick(int position, long itemId) { + Model model = mAdapter.getItem(position); + if (model != null) + onItemClick(model, position); + } + + @Override + public void onRefreshing() { + if (mPresenter == null) + return; + mAdapter.setState(BaseRecyclerAdapter.STATE_HIDE, true); + mPresenter.onRefreshing(); + } + + @Override + public void onLoadMore() { + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + mPresenter.onLoadMore(); + } + + @Override + public void onScrollToBottom() { + + } + + @Override + public void onRefreshSuccess(List data) { + mAdapter.resetItem(data); + } + + @Override + public void onLoadMoreSuccess(List data) { + mAdapter.addAll(data); + } + + @Override + public void showNotMore() { + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + } + + @Override + public void showNetworkError(int strId) { + mAdapter.setState(BaseRecyclerAdapter.STATE_INVALID_NETWORK, true); + mAdapter.setState(BaseRecyclerAdapter.STATE_LOAD, true); + } + + @Override + public void onComplete() { + mRefreshLayout.onComplete(); + } + + @Override + public void setPresenter(Presenter presenter) { + this.mPresenter = presenter; + } + + protected RecyclerView.LayoutManager getLayoutManager() { + return new LinearLayoutManager(mContext); + } + + @Override + public Date getSystemTime() { + return new Date(); + } + + protected abstract BaseRecyclerAdapter getAdapter(); + + protected abstract void onItemClick(Model model, int position); +} diff --git a/app/src/main/java/net/oschina/app/improve/base/BaseView.java b/app/src/main/java/net/oschina/app/improve/base/BaseView.java new file mode 100644 index 0000000000000000000000000000000000000000..b0a1a09d6b9240b7601051b91738a547d81243fd --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/BaseView.java @@ -0,0 +1,13 @@ +package net.oschina.app.improve.base; + +/** + * Created by haibin + * on 2016/11/30. + */ + +public interface BaseView { + + void setPresenter(Presenter presenter); + + void showNetworkError(int strId); +} diff --git a/app/src/main/java/net/oschina/app/improve/base/activities/BackActivity.java b/app/src/main/java/net/oschina/app/improve/base/activities/BackActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..204a4130fb6535a7ce0c92867126c1e2a3712e3e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/activities/BackActivity.java @@ -0,0 +1,62 @@ +package net.oschina.app.improve.base.activities; + +import android.app.ProgressDialog; +import android.graphics.Color; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.widget.Toolbar; + +import net.oschina.app.R; +import net.oschina.app.improve.utils.DialogHelper; + +/** + * Created by haibin + * on 2016/12/1. + */ + +public abstract class BackActivity extends BaseActivity { + protected Toolbar mToolBar; + private ProgressDialog mWaitDialog; + + @Override + protected void initWindow() { + super.initWindow(); + mToolBar = (Toolbar) findViewById(R.id.toolbar); + if (mToolBar != null) { + setSupportActionBar(mToolBar); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setHomeButtonEnabled(false); + } + } + } + + + @SuppressWarnings("ConstantConditions") + protected void setDarkToolBar() { + if (mToolBar != null) { + mToolBar.setTitleTextColor(Color.BLACK); + DrawableCompat.setTint(mToolBar.getNavigationIcon(), Color.BLACK); + } + } + + @Override + public boolean onSupportNavigateUp() { + finish(); + return super.onSupportNavigateUp(); + } + + protected void showLoadingDialog(String message) { + if (mWaitDialog == null) { + mWaitDialog = DialogHelper.getProgressDialog(this, true); + } + mWaitDialog.setMessage(message); + mWaitDialog.show(); + } + + protected void dismissLoadingDialog() { + if (mWaitDialog == null) return; + mWaitDialog.dismiss(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/activities/BaseActivity.java b/app/src/main/java/net/oschina/app/improve/base/activities/BaseActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..0d87118bed983f2b3a815ca9f6a79ad95ef77170 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/activities/BaseActivity.java @@ -0,0 +1,323 @@ +package net.oschina.app.improve.base.activities; + +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +import com.baidu.mobstat.StatService; +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.swipe.SwipeBackActivity; +import net.oschina.app.improve.main.ClipManager; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; + +import butterknife.ButterKnife; + +/** + * Created by JuQiu + * on 16/6/20. + */ + +public abstract class BaseActivity extends SwipeBackActivity { + protected RequestManager mImageLoader; + private boolean mIsDestroy; + public static boolean IS_ACTIVE = true; + private static boolean isMiUi = false; + public static boolean hasSetStatusBarColor;//是否需要单独设置状态栏颜色 + private Fragment mFragment; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setSwipeBackEnable(true); + if (initBundle(getIntent().getExtras())) { + setContentView(getContentView()); + + initWindow(); + + ButterKnife.bind(this); + initWidget(); + initData(); + } else { + finish(); + } + + StatService.setDebugOn(false); + //umeng analytics +// MobclickAgent.setDebugMode(false); +// MobclickAgent.openActivityDurationTrack(false); +// MobclickAgent.setScenarioType(this, MobclickAgent.EScenarioType.E_UM_NORMAL); + } + + protected void addFragment(int frameLayoutId, Fragment fragment) { + if (fragment != null) { + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + if (fragment.isAdded()) { + if (mFragment != null) { + transaction.hide(mFragment).show(fragment); + } else { + transaction.show(fragment); + } + } else { + if (mFragment != null) { + transaction.hide(mFragment).add(frameLayoutId, fragment); + } else { + transaction.add(frameLayoutId, fragment); + } + } + mFragment = fragment; + transaction.commit(); + } + } + + @SuppressWarnings("unused") + protected void replaceFragment(int frameLayoutId, Fragment fragment) { + if (fragment != null) { + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(frameLayoutId, fragment); + transaction.commit(); + } + } + + @Override + protected void onResume() { + super.onResume(); + ClipManager.onResume(); + StatService.onResume(this); +// MobclickAgent.onPageStart(this.mPackageNameUmeng); +// MobclickAgent.onResume(this); + } + + @Override + protected void onPause() { + super.onPause(); + StatService.onPause(this); +// MobclickAgent.onPageEnd(this.mPackageNameUmeng); +// MobclickAgent.onPause(this); + } + + @Override + protected void onStop() { + super.onStop(); + IS_ACTIVE = isOnForeground(); + } + + protected abstract int getContentView(); + + protected boolean initBundle(Bundle bundle) { + return true; + } + + protected void initWindow() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Window window = getWindow(); + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(Color.TRANSPARENT); + + } + } + + protected void initWidget() { + } + + protected void initData() { + } + + public synchronized RequestManager getImageLoader() { + if (mImageLoader == null) + mImageLoader = Glide.with(this); + return mImageLoader; + } + + + @Override + protected void onDestroy() { + mIsDestroy = true; + super.onDestroy(); + } + + public boolean isDestroy() { + return mIsDestroy; + } + + + @SuppressLint("PrivateApi") + private void setMIUIStatusBarDarkMode() { + if (isMiUi) { + Class clazz = getWindow().getClass(); + try { + int darkModeFlag; + Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); + Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); + darkModeFlag = field.getInt(layoutParams); + Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); + extraFlagField.invoke(getWindow(), darkModeFlag, darkModeFlag); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /* + * 静态域,获取系统版本是否基于MIUI + */ + + static { + try { + @SuppressLint("PrivateApi") Class sysClass = Class.forName("android.os.SystemProperties"); + Method getStringMethod = sysClass.getDeclaredMethod("get", String.class); + String version = (String) getStringMethod.invoke(sysClass, "ro.miui.ui.version.name"); + isMiUi = !(version.compareTo("V9") >= 0 && Build.VERSION.SDK_INT >= 23) && version.compareTo("V6") >= 0 && Build.VERSION.SDK_INT < 24; + + } catch (Exception e) { + e.printStackTrace(); + } + } + + + /** + * 设置魅族手机状态栏图标颜色风格 + * 可以用来判断是否为Flyme用户 + * + * @param window 需要设置的窗口 + * @return boolean 成功执行返回true + */ + @SuppressWarnings("JavaReflectionMemberAccess") + private static boolean setMeizuDarkMode(Window window) { + boolean result = false; + if (Build.VERSION.SDK_INT >= 24) { + return false; + } + if (window != null) { + try { + WindowManager.LayoutParams lp = window.getAttributes(); + Field darkFlag = WindowManager.LayoutParams.class + .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); + Field meizuFlags = WindowManager.LayoutParams.class + .getDeclaredField("meizuFlags"); + darkFlag.setAccessible(true); + meizuFlags.setAccessible(true); + int bit = darkFlag.getInt(null); + int value = meizuFlags.getInt(lp); + value |= bit; + meizuFlags.setInt(lp, value); + window.setAttributes(lp); + result = true; + } catch (Exception e) { + e.printStackTrace(); + } + } + return result; + } + + @SuppressLint("InlinedApi") + private int getStatusBarLightMode() { + int result = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (isMiUi) { + result = 1; + } else if (setMeizuDarkMode(getWindow())) { + result = 2; + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + result = 3; + } else { + result = 4; + } + } + return result; + } + + /** + * 是否设置状态栏颜色 + * + * @return return + */ + protected boolean isSetStatusBarColor() { + return true; + } + + @SuppressLint("InlinedApi") + protected void setStatusBarDarkMode() { + int type = getStatusBarLightMode(); + if (type == 1) { + setMIUIStatusBarDarkMode(); + } else if (type == 2) { + setMeizuDarkMode(getWindow()); + } else if (type == 3) { + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } else if (type == 4) { + hasSetStatusBarColor = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isSetStatusBarColor()) { + Window window = getWindow(); + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(getResources().getColor(R.color.status_bar_color)); + + } + } + } + + /** + * 是否在前台 + * + * @return isOnForeground APP是否在前台 + */ + protected boolean isOnForeground() { + ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE); + String packageName = getApplicationContext().getPackageName(); + + assert activityManager != null; + List appProcesses = activityManager + .getRunningAppProcesses(); + if (appProcesses == null) + return false; + + for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { + if (appProcess.processName.equals(packageName) + && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { + return true; + } + } + return false; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (newConfig.fontScale != 1)//非默认值 + getResources(); + super.onConfigurationChanged(newConfig); + } + + @Override + public Resources getResources() { + Resources res = super.getResources(); + if (res.getConfiguration().fontScale != 1) {//非默认值 + Configuration newConfig = new Configuration(); + newConfig.setToDefaults();//设置默认 + res.updateConfiguration(newConfig, res.getDisplayMetrics()); + } + return res; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/activities/BaseBackActivity.java b/app/src/main/java/net/oschina/app/improve/base/activities/BaseBackActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..e342051b4c8c37efda7e7a6f152bc800ab4cc934 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/activities/BaseBackActivity.java @@ -0,0 +1,51 @@ +package net.oschina.app.improve.base.activities; + +import android.app.ProgressDialog; +import android.support.v7.app.ActionBar; + +import net.oschina.app.improve.utils.DialogHelper; + +/** + * Created by JuQiu + * on 16/6/20. + */ + +public abstract class BaseBackActivity extends BaseActivity { + + private ProgressDialog mWaitDialog; + + @Override + protected void initWindow() { + super.initWindow(); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setHomeButtonEnabled(false); + } + } + + @Override + public boolean onSupportNavigateUp() { + finish(); + return super.onSupportNavigateUp(); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + finish(); + } + + protected void showLoadingDialog(String message) { + if (mWaitDialog == null) { + mWaitDialog = DialogHelper.getProgressDialog(this, true); + } + mWaitDialog.setMessage(message); + mWaitDialog.show(); + } + + protected void dismissLoadingDialog() { + if (mWaitDialog == null) return; + mWaitDialog.dismiss(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/activities/BaseRecyclerViewActivity.java b/app/src/main/java/net/oschina/app/improve/base/activities/BaseRecyclerViewActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..919ec67ec5d33884ae37a410bb0b62121c156508 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/activities/BaseRecyclerViewActivity.java @@ -0,0 +1,206 @@ +package net.oschina.app.improve.base.activities; + +import android.content.Context; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; + +import com.bumptech.glide.RequestManager; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.widget.RecyclerRefreshLayout; +import net.oschina.app.improve.widget.SimplexToast; + +import java.lang.reflect.Type; +import java.util.Date; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * Created by huanghaibin_dev + * on 16-6-23. + */ +public abstract class BaseRecyclerViewActivity extends BackActivity implements + BaseRecyclerAdapter.OnItemClickListener, + RecyclerRefreshLayout.SuperRefreshLayoutListener, + BaseGeneralRecyclerAdapter.Callback { + + @Bind(R.id.refreshLayout) + protected RecyclerRefreshLayout mRefreshLayout; + + @Bind(R.id.recyclerView) + protected RecyclerView mRecyclerView; + + protected BaseRecyclerAdapter mAdapter; + + protected TextHttpResponseHandler mHandler; + + protected PageBean mBean; + + protected boolean mIsRefresh; + + @Override + protected int getContentView() { + return R.layout.activity_base_recycler; + } + + @Override + protected void initWidget() { + super.initWidget(); + mAdapter = mAdapter == null ? getRecyclerAdapter() : mAdapter; + mRecyclerView.setLayoutManager(getLayoutManager()); + mRecyclerView.setAdapter(mAdapter); + mAdapter.setOnItemClickListener(this); + mRefreshLayout.setSuperRefreshLayoutListener(this); + mRefreshLayout.setColorSchemeResources( + R.color.swiperefresh_color1, R.color.swiperefresh_color2, + R.color.swiperefresh_color3, R.color.swiperefresh_color4); + } + + @Override + protected void initData() { + super.initData(); + mBean = new PageBean<>(); + mHandler = new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + onLoadingFailure(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> resultBean = AppOperator.createGson().fromJson(responseString, getType()); + if (resultBean != null && resultBean.isSuccess() && resultBean.getResult().getItems() != null) { + onLoadingSuccess(); + setListData(resultBean); + } else { + if (resultBean.getCode() == ResultBean.RESULT_TOKEN_ERROR) { + SimplexToast.show(BaseRecyclerViewActivity.this, resultBean.getMessage()); + } + onLoadingFailure(); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + + @Override + public void onStart() { + super.onStart(); + onLoadingStart(); + } + + @Override + public void onFinish() { + super.onFinish(); + onLoadingFinish(); + } + }; + + mRefreshLayout.post(new Runnable() { + @Override + public void run() { + mRefreshLayout.setRefreshing(true); + onRefreshing(); + } + }); + } + + @Override + public void onItemClick(int position, long itemId) { + onItemClick(mAdapter.getItem(position), position); + } + + @Override + public void onRefreshing() { + mIsRefresh = true; + requestData(); + } + + @Override + public void onLoadMore() { + mAdapter.setState(mRefreshLayout.isRefreshing() ? BaseRecyclerAdapter.STATE_HIDE : BaseRecyclerAdapter.STATE_LOADING, true); + requestData(); + } + + @Override + public void onScrollToBottom() { + + } + + protected void onItemClick(T item, int position) { + + } + + protected void requestData() { + + } + + protected void setListData(ResultBean> resultBean) { + mBean.setNextPageToken(resultBean.getResult().getNextPageToken()); + mBean.setPrevPageToken(resultBean.getResult().getPrevPageToken()); + if (mIsRefresh) { + mBean.setItems(resultBean.getResult().getItems()); + mAdapter.clear(); + mAdapter.addAll(mBean.getItems()); + mBean.setPrevPageToken(resultBean.getResult().getPrevPageToken()); + mRefreshLayout.setCanLoadMore(true); + } else { + mAdapter.addAll(resultBean.getResult().getItems()); + } + mAdapter.setState(resultBean.getResult().getItems() == null || resultBean.getResult().getItems().size() < 20 ? BaseRecyclerAdapter.STATE_NO_MORE : BaseRecyclerAdapter.STATE_LOADING, true); + } + + protected void onLoadingStart() { + + } + + protected void onLoadingSuccess() { + + } + + protected void onLoadingFinish() { + mRefreshLayout.onComplete(); + mIsRefresh = false; + } + + protected void onLoadingFailure() { + if (mAdapter.getItems().size() == 0) { + mAdapter.setState(BaseRecyclerAdapter.STATE_LOAD_ERROR, true); + } else { + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + } + } + + + protected RecyclerView.LayoutManager getLayoutManager() { + return new LinearLayoutManager(this); + } + + protected abstract Type getType(); + + protected abstract BaseRecyclerAdapter getRecyclerAdapter(); + + @Override + public RequestManager getImgLoader() { + return getImageLoader(); + } + + @Override + public Context getContext() { + return this; + } + + @Override + public Date getSystemTime() { + return new Date(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackActivity.java b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..28061015ed68baf8be3c2f7f9670d8a07e222e03 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackActivity.java @@ -0,0 +1,47 @@ + +package net.oschina.app.improve.base.activities.swipe; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; + +public class SwipeBackActivity extends AppCompatActivity implements SwipeBackActivityBase { + private SwipeBackActivityHelper mHelper; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mHelper = new SwipeBackActivityHelper(this); + mHelper.onActivityCreate(); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + mHelper.onPostCreate(); + } + + @Override + public View findViewById(int id) { + View v = super.findViewById(id); + if (v == null && mHelper != null) + return mHelper.findViewById(id); + return v; + } + + @Override + public SwipeBackLayout getSwipeBackLayout() { + return mHelper.getSwipeBackLayout(); + } + + @Override + public void setSwipeBackEnable(boolean enable) { + getSwipeBackLayout().setEnableGesture(enable); + } + + @Override + public void scrollToFinishActivity() { + Utils.convertActivityToTranslucent(this); + getSwipeBackLayout().scrollToFinishActivity(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackActivityBase.java b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackActivityBase.java new file mode 100644 index 0000000000000000000000000000000000000000..caae00282855b318a0ad25643d9d6f8166431b28 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackActivityBase.java @@ -0,0 +1,18 @@ +package net.oschina.app.improve.base.activities.swipe; +/** + * @author Yrom + */ + interface SwipeBackActivityBase { + /** + * @return the SwipeBackLayout associated with this activity. + */ + SwipeBackLayout getSwipeBackLayout(); + + void setSwipeBackEnable(boolean enable); + + /** + * Scroll out contentView and finish the activity + */ + void scrollToFinishActivity(); + +} diff --git a/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackActivityHelper.java b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackActivityHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..74f28a0f414b5aa4a6776a8bf5f50624fd9c9b96 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackActivityHelper.java @@ -0,0 +1,63 @@ +package net.oschina.app.improve.base.activities.swipe; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.view.LayoutInflater; +import android.view.View; + +import net.oschina.app.R; + + +/** + * @author Yrom + */ + class SwipeBackActivityHelper { + private Activity mActivity; + + private SwipeBackLayout mSwipeBackLayout; + + SwipeBackActivityHelper(Activity activity) { + mActivity = activity; + } + + @SuppressLint("InflateParams") + @SuppressWarnings("deprecation") + void onActivityCreate() { + mActivity.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + mActivity.getWindow().getDecorView().setBackgroundDrawable(null); + mSwipeBackLayout = (SwipeBackLayout) LayoutInflater.from(mActivity).inflate( + R.layout.swipeback_layout, null); + mSwipeBackLayout.addSwipeListener(new SwipeBackLayout.SwipeListener() { + @Override + public void onScrollStateChange(int state, float scrollPercent) { + } + + @Override + public void onEdgeTouch(int edgeFlag) { + Utils.convertActivityToTranslucent(mActivity); + } + + @Override + public void onScrollOverThreshold() { + + } + }); + } + + void onPostCreate() { + mSwipeBackLayout.attachToActivity(mActivity); + } + + View findViewById(int id) { + if (mSwipeBackLayout != null) { + return mSwipeBackLayout.findViewById(id); + } + return null; + } + + SwipeBackLayout getSwipeBackLayout() { + return mSwipeBackLayout; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackLayout.java b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..d7017619cbfdfbdb6a2c62d007c81822f71d9a92 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackLayout.java @@ -0,0 +1,611 @@ +package net.oschina.app.improve.base.activities.swipe; + +import android.app.Activity; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import net.oschina.app.R; + +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("all") +public class SwipeBackLayout extends FrameLayout { + /** + * Minimum velocity that will be detected as a fling + */ + private static final int MIN_FLING_VELOCITY = 400; // dips per second + + private static final int DEFAULT_SCRIM_COLOR = 0x99000000; + + private static final int FULL_ALPHA = 255; + + /** + * Edge flag indicating that the left edge should be affected. + */ + public static final int EDGE_LEFT = ViewDragHelper.EDGE_LEFT; + + /** + * Edge flag indicating that the right edge should be affected. + */ + public static final int EDGE_RIGHT = ViewDragHelper.EDGE_RIGHT; + + /** + * Edge flag indicating that the bottom edge should be affected. + */ + public static final int EDGE_BOTTOM = ViewDragHelper.EDGE_BOTTOM; + + /** + * Edge flag set indicating all edges should be affected. + */ + public static final int EDGE_ALL = EDGE_LEFT | EDGE_RIGHT | EDGE_BOTTOM; + + /** + * A view is not currently being dragged or animating as a result of a + * fling/snap. + */ + public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE; + + /** + * 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 = ViewDragHelper.STATE_DRAGGING; + + /** + * A view is currently settling into place as a result of a fling or + * predefined non-interactive motion. + */ + public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING; + + /** + * Default threshold of scroll + */ + private static final float DEFAULT_SCROLL_THRESHOLD = 0.3f; + + private static final int OVERSCROLL_DISTANCE = 10; + + private static final int[] EDGE_FLAGS = { + EDGE_LEFT, EDGE_RIGHT, EDGE_BOTTOM, EDGE_ALL + }; + + private int mEdgeFlag; + + /** + * Threshold of scroll, we will close the activity, when scrollPercent over + * this value; + */ + private float mScrollThreshold = DEFAULT_SCROLL_THRESHOLD; + + private Activity mActivity; + + private boolean mEnable = true; + + private View mContentView; + + private ViewDragHelper mDragHelper; + + private float mScrollPercent; + + private int mContentLeft; + + private int mContentTop; + + /** + * The set of listeners to be sent events through. + */ + private List mListeners; + + private Drawable mShadowLeft; + + private Drawable mShadowRight; + + private Drawable mShadowBottom; + + private float mScrimOpacity; + + private int mScrimColor = DEFAULT_SCRIM_COLOR; + + private boolean mInLayout; + + private Rect mTmpRect = new Rect(); + + /** + * Edge being dragged + */ + private int mTrackingEdge; + + public SwipeBackLayout(Context context) { + this(context, null); + } + + public SwipeBackLayout(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.SwipeBackLayoutStyle); + } + + public SwipeBackLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs); + mDragHelper = ViewDragHelper.create(this, new ViewDragCallback()); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeBackLayout, defStyle, + R.style.SwipeBackLayout); + + int edgeSize = a.getDimensionPixelSize(R.styleable.SwipeBackLayout_edge_size, -1); + if (edgeSize > 0) + setEdgeSize(edgeSize); + int mode = EDGE_FLAGS[a.getInt(R.styleable.SwipeBackLayout_edge_flag, 0)]; + setEdgeTrackingEnabled(mode); + + int shadowLeft = a.getResourceId(R.styleable.SwipeBackLayout_shadow_left, + R.drawable.shadow_left); + int shadowRight = a.getResourceId(R.styleable.SwipeBackLayout_shadow_right, + R.drawable.shadow_right); + int shadowBottom = a.getResourceId(R.styleable.SwipeBackLayout_shadow_bottom, + R.drawable.shadow_bottom); + setShadow(shadowLeft, EDGE_LEFT); + setShadow(shadowRight, EDGE_RIGHT); + setShadow(shadowBottom, EDGE_BOTTOM); + a.recycle(); + final float density = getResources().getDisplayMetrics().density; + final float minVel = MIN_FLING_VELOCITY * density; + mDragHelper.setMinVelocity(minVel); + mDragHelper.setMaxVelocity(minVel * 2f); + } + + /** + * Sets the sensitivity of the NavigationLayout. + * + * @param context The application context. + * @param sensitivity value between 0 and 1, the final value for touchSlop = + * ViewConfiguration.getScaledTouchSlop * (1 / s); + */ + public void setSensitivity(Context context, float sensitivity) { + mDragHelper.setSensitivity(context, sensitivity); + } + + /** + * Set up contentView which will be moved by user gesture + * + * @param view + */ + private void setContentView(View view) { + mContentView = view; + } + + public void setEnableGesture(boolean enable) { + mEnable = enable; + } + + /** + * Enable edge tracking for the selected edges of the parent view. The + * callback's + * {@link ViewDragHelper.Callback#onEdgeTouched(int, int)} + * and + * {@link ViewDragHelper.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_RIGHT + * @see #EDGE_BOTTOM + */ + public void setEdgeTrackingEnabled(int edgeFlags) { + mEdgeFlag = edgeFlags; + mDragHelper.setEdgeTrackingEnabled(mEdgeFlag); + } + + /** + * 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(int color) { + mScrimColor = color; + invalidate(); + } + + /** + * Set 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. + * + * @param size The size of an edge in pixels + */ + public void setEdgeSize(int size) { + mDragHelper.setEdgeSize(size); + } + + /** + * Register a callback to be invoked when a swipe event is sent to this + * view. + * + * @param listener the swipe listener to attach to this view + * @deprecated use {@link #addSwipeListener} instead + */ + @Deprecated + public void setSwipeListener(SwipeListener listener) { + addSwipeListener(listener); + } + + /** + * Add a callback to be invoked when a swipe event is sent to this view. + * + * @param listener the swipe listener to attach to this view + */ + public void addSwipeListener(SwipeListener listener) { + if (mListeners == null) { + mListeners = new ArrayList<>(); + } + mListeners.add(listener); + } + + /** + * Removes a listener from the set of listeners + * + * @param listener + */ + public void removeSwipeListener(SwipeListener listener) { + if (mListeners == null) { + return; + } + mListeners.remove(listener); + } + + public interface SwipeListener { + /** + * Invoke when state change + * + * @param state flag to describe scroll state + * @param scrollPercent scroll percent of this view + * @see #STATE_IDLE + * @see #STATE_DRAGGING + * @see #STATE_SETTLING + */ + void onScrollStateChange(int state, float scrollPercent); + + /** + * Invoke when edge touched + * + * @param edgeFlag edge flag describing the edge being touched + * @see #EDGE_LEFT + * @see #EDGE_RIGHT + * @see #EDGE_BOTTOM + */ + void onEdgeTouch(int edgeFlag); + + /** + * Invoke when scroll percent over the threshold for the first time + */ + void onScrollOverThreshold(); + } + + /** + * Set scroll threshold, we will close the activity, when scrollPercent over + * this value + * + * @param threshold + */ + public void setScrollThresHold(float threshold) { + if (threshold >= 1.0f || threshold <= 0) { + throw new IllegalArgumentException("Threshold value should be between 0 and 1.0"); + } + mScrollThreshold = threshold; + } + + /** + * Set a drawable used for edge shadow. + * + * @param shadow Drawable to use + * @param edgeFlag Combination of edge flags describing the edge to set + * @see #EDGE_LEFT + * @see #EDGE_RIGHT + * @see #EDGE_BOTTOM + */ + public void setShadow(Drawable shadow, int edgeFlag) { + if ((edgeFlag & EDGE_LEFT) != 0) { + mShadowLeft = shadow; + } else if ((edgeFlag & EDGE_RIGHT) != 0) { + mShadowRight = shadow; + } else if ((edgeFlag & EDGE_BOTTOM) != 0) { + mShadowBottom = shadow; + } + invalidate(); + } + + /** + * Set a drawable used for edge shadow. + * + * @param resId Resource of drawable to use + * @param edgeFlag Combination of edge flags describing the edge to set + * @see #EDGE_LEFT + * @see #EDGE_RIGHT + * @see #EDGE_BOTTOM + */ + public void setShadow(int resId, int edgeFlag) { + setShadow(getResources().getDrawable(resId), edgeFlag); + } + + /** + * Scroll out contentView and finish the activity + */ + public void scrollToFinishActivity() { + final int childWidth = mContentView.getWidth(); + final int childHeight = mContentView.getHeight(); + + int left = 0, top = 0; + if ((mEdgeFlag & EDGE_LEFT) != 0) { + left = childWidth + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE; + mTrackingEdge = EDGE_LEFT; + } else if ((mEdgeFlag & EDGE_RIGHT) != 0) { + left = -childWidth - mShadowRight.getIntrinsicWidth() - OVERSCROLL_DISTANCE; + mTrackingEdge = EDGE_RIGHT; + } else if ((mEdgeFlag & EDGE_BOTTOM) != 0) { + top = -childHeight - mShadowBottom.getIntrinsicHeight() - OVERSCROLL_DISTANCE; + mTrackingEdge = EDGE_BOTTOM; + } + + mDragHelper.smoothSlideViewTo(mContentView, left, top); + invalidate(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + if (!mEnable) { + return false; + } + try { + return mDragHelper.shouldInterceptTouchEvent(event); + } catch (ArrayIndexOutOfBoundsException e) { + // FIXME: handle exception + // issues #9 + return false; + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!mEnable) { + return false; + } + mDragHelper.processTouchEvent(event); + return true; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + mInLayout = true; + if (mContentView != null) + mContentView.layout(mContentLeft, mContentTop, + mContentLeft + mContentView.getMeasuredWidth(), + mContentTop + mContentView.getMeasuredHeight()); + mInLayout = false; + } + + @Override + public void requestLayout() { + if (!mInLayout) { + super.requestLayout(); + } + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + final boolean drawContent = child == mContentView; + + boolean ret = super.drawChild(canvas, child, drawingTime); + if (mScrimOpacity > 0 && drawContent + && mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) { + drawShadow(canvas, child); + drawScrim(canvas, child); + } + return ret; + } + + private void drawScrim(Canvas canvas, View child) { + final int baseAlpha = (mScrimColor & 0xff000000) >>> 24; + final int alpha = (int) (baseAlpha * mScrimOpacity); + final int color = alpha << 24 | (mScrimColor & 0xffffff); + + if ((mTrackingEdge & EDGE_LEFT) != 0) { + canvas.clipRect(0, 0, child.getLeft(), getHeight()); + } else if ((mTrackingEdge & EDGE_RIGHT) != 0) { + canvas.clipRect(child.getRight(), 0, getRight(), getHeight()); + } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) { + canvas.clipRect(child.getLeft(), child.getBottom(), getRight(), getHeight()); + } + canvas.drawColor(color); + } + + private void drawShadow(Canvas canvas, View child) { + final Rect childRect = mTmpRect; + child.getHitRect(childRect); + + if ((mEdgeFlag & EDGE_LEFT) != 0) { + mShadowLeft.setBounds(childRect.left - mShadowLeft.getIntrinsicWidth(), childRect.top, + childRect.left, childRect.bottom); + mShadowLeft.setAlpha((int) (mScrimOpacity * FULL_ALPHA)); + mShadowLeft.draw(canvas); + } + + if ((mEdgeFlag & EDGE_RIGHT) != 0) { + mShadowRight.setBounds(childRect.right, childRect.top, + childRect.right + mShadowRight.getIntrinsicWidth(), childRect.bottom); + mShadowRight.setAlpha((int) (mScrimOpacity * FULL_ALPHA)); + mShadowRight.draw(canvas); + } + + if ((mEdgeFlag & EDGE_BOTTOM) != 0) { + mShadowBottom.setBounds(childRect.left, childRect.bottom, childRect.right, + childRect.bottom + mShadowBottom.getIntrinsicHeight()); + mShadowBottom.setAlpha((int) (mScrimOpacity * FULL_ALPHA)); + mShadowBottom.draw(canvas); + } + } + + public void attachToActivity(Activity activity) { + mActivity = activity; + TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{ + android.R.attr.windowBackground + }); + int background = a.getResourceId(0, 0); + a.recycle(); + + ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView(); + ViewGroup decorChild = (ViewGroup) decor.getChildAt(0); + decorChild.setBackgroundResource(background); + decor.removeView(decorChild); + addView(decorChild); + setContentView(decorChild); + decor.addView(this); + } + + @Override + public void computeScroll() { + mScrimOpacity = 1 - mScrollPercent; + if (mDragHelper.continueSettling(true)) { + ViewCompat.postInvalidateOnAnimation(this); + } + } + + private class ViewDragCallback extends ViewDragHelper.Callback { + private boolean mIsScrollOverValid; + + @Override + public boolean tryCaptureView(View view, int i) { + boolean ret = mDragHelper.isEdgeTouched(mEdgeFlag, i); + if (ret) { + if (mDragHelper.isEdgeTouched(EDGE_LEFT, i)) { + mTrackingEdge = EDGE_LEFT; + } else if (mDragHelper.isEdgeTouched(EDGE_RIGHT, i)) { + mTrackingEdge = EDGE_RIGHT; + } else if (mDragHelper.isEdgeTouched(EDGE_BOTTOM, i)) { + mTrackingEdge = EDGE_BOTTOM; + } + if (mListeners != null && !mListeners.isEmpty()) { + for (SwipeListener listener : mListeners) { + listener.onEdgeTouch(mTrackingEdge); + } + } + mIsScrollOverValid = true; + } + boolean directionCheck = false; + if (mEdgeFlag == EDGE_LEFT || mEdgeFlag == EDGE_RIGHT) { + directionCheck = !mDragHelper.checkTouchSlop(ViewDragHelper.DIRECTION_VERTICAL, i); + } else if (mEdgeFlag == EDGE_BOTTOM) { + directionCheck = !mDragHelper + .checkTouchSlop(ViewDragHelper.DIRECTION_HORIZONTAL, i); + } else if (mEdgeFlag == EDGE_ALL) { + directionCheck = true; + } + return ret & directionCheck; + } + + @Override + public int getViewHorizontalDragRange(View child) { + return mEdgeFlag & (EDGE_LEFT | EDGE_RIGHT); + } + + @Override + public int getViewVerticalDragRange(View child) { + return mEdgeFlag & EDGE_BOTTOM; + } + + @Override + public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { + super.onViewPositionChanged(changedView, left, top, dx, dy); + if ((mTrackingEdge & EDGE_LEFT) != 0) { + mScrollPercent = Math.abs((float) left + / (mContentView.getWidth() + mShadowLeft.getIntrinsicWidth())); + } else if ((mTrackingEdge & EDGE_RIGHT) != 0) { + mScrollPercent = Math.abs((float) left + / (mContentView.getWidth() + mShadowRight.getIntrinsicWidth())); + } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) { + mScrollPercent = Math.abs((float) top + / (mContentView.getHeight() + mShadowBottom.getIntrinsicHeight())); + } + mContentLeft = left; + mContentTop = top; + invalidate(); + if (mScrollPercent < mScrollThreshold && !mIsScrollOverValid) { + mIsScrollOverValid = true; + } + if (mListeners != null && !mListeners.isEmpty() + && mDragHelper.getViewDragState() == STATE_DRAGGING + && mScrollPercent >= mScrollThreshold && mIsScrollOverValid) { + mIsScrollOverValid = false; + for (SwipeListener listener : mListeners) { + listener.onScrollOverThreshold(); + } + } + + if (mScrollPercent >= 1) { + if (!mActivity.isFinishing()) { + mActivity.finish(); + mActivity.overridePendingTransition(0, 0); + } + } + } + + @Override + public void onViewReleased(View releasedChild, float xvel, float yvel) { + final int childWidth = releasedChild.getWidth(); + final int childHeight = releasedChild.getHeight(); + + int left = 0, top = 0; + if ((mTrackingEdge & EDGE_LEFT) != 0) { + left = xvel > 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? childWidth + + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE : 0; + } else if ((mTrackingEdge & EDGE_RIGHT) != 0) { + left = xvel < 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? -(childWidth + + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE) : 0; + } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) { + top = yvel < 0 || yvel == 0 && mScrollPercent > mScrollThreshold ? -(childHeight + + mShadowBottom.getIntrinsicHeight() + OVERSCROLL_DISTANCE) : 0; + } + + mDragHelper.settleCapturedViewAt(left, top); + invalidate(); + } + + @Override + public int clampViewPositionHorizontal(View child, int left, int dx) { + int ret = 0; + if ((mTrackingEdge & EDGE_LEFT) != 0) { + ret = Math.min(child.getWidth(), Math.max(left, 0)); + } else if ((mTrackingEdge & EDGE_RIGHT) != 0) { + ret = Math.min(0, Math.max(left, -child.getWidth())); + } + return ret; + } + + @Override + public int clampViewPositionVertical(View child, int top, int dy) { + int ret = 0; + if ((mTrackingEdge & EDGE_BOTTOM) != 0) { + ret = Math.min(0, Math.max(top, -child.getHeight())); + } + return ret; + } + + @Override + public void onViewDragStateChanged(int state) { + super.onViewDragStateChanged(state); + if (mListeners != null && !mListeners.isEmpty()) { + for (SwipeListener listener : mListeners) { + listener.onScrollStateChange(state, mScrollPercent); + } + } + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackPreferenceActivity.java b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackPreferenceActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..1a154c459e866ca29c8b2a562467b545ebf9a676 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/SwipeBackPreferenceActivity.java @@ -0,0 +1,45 @@ + +package net.oschina.app.improve.base.activities.swipe; + +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.view.View; + +public class SwipeBackPreferenceActivity extends PreferenceActivity implements SwipeBackActivityBase { + private SwipeBackActivityHelper mHelper; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mHelper = new SwipeBackActivityHelper(this); + mHelper.onActivityCreate(); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + mHelper.onPostCreate(); + } + + @Override + public View findViewById(int id) { + View v = super.findViewById(id); + if (v == null && mHelper != null) + return mHelper.findViewById(id); + return v; + } + + @Override + public SwipeBackLayout getSwipeBackLayout() { + return mHelper.getSwipeBackLayout(); + } + @Override + public void setSwipeBackEnable(boolean enable) { + getSwipeBackLayout().setEnableGesture(enable); + } + + @Override + public void scrollToFinishActivity() { + getSwipeBackLayout().scrollToFinishActivity(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/activities/swipe/Utils.java b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/Utils.java new file mode 100644 index 0000000000000000000000000000000000000000..b992b725a88cf2acbb8a6dc2674acf476708d28d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/Utils.java @@ -0,0 +1,104 @@ + +package net.oschina.app.improve.base.activities.swipe; + +import android.app.Activity; +import android.app.ActivityOptions; +import android.os.Build; + +import java.lang.reflect.Method; + +/** + * + * Created by Chaojun Wang on 6/9/14. + */ + class Utils { + private Utils() { + } + + /** + * Convert a translucent themed Activity + * {@link android.R.attr#windowIsTranslucent} to a fullscreen opaque + * Activity. + *

    + * Call this whenever the background of a translucent Activity has changed + * to become opaque. Doing so will allow the {@link android.view.Surface} of + * the Activity behind to be released. + *

    + * This call has no effect on non-translucent activities or on activities + * with the {@link android.R.attr#windowIsFloating} attribute. + */ + static void convertActivityFromTranslucent(Activity activity) { + try { + Method method = Activity.class.getDeclaredMethod("convertFromTranslucent"); + method.setAccessible(true); + method.invoke(activity); + } catch (Throwable t) { + } + } + + /** + * Convert a translucent themed Activity + * {@link android.R.attr#windowIsTranslucent} back from opaque to + * translucent following a call to + * {@link #convertActivityFromTranslucent(Activity)} . + *

    + * Calling this allows the Activity behind this one to be seen again. Once + * all such Activities have been redrawn + *

    + * This call has no effect on non-translucent activities or on activities + * with the {@link android.R.attr#windowIsFloating} attribute. + */ + static void convertActivityToTranslucent(Activity activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + convertActivityToTranslucentAfterL(activity); + } else { + convertActivityToTranslucentBeforeL(activity); + } + } + + /** + * Calling the convertToTranslucent method on platforms before Android 5.0 + */ + static void convertActivityToTranslucentBeforeL(Activity activity) { + try { + Class[] classes = Activity.class.getDeclaredClasses(); + Class translucentConversionListenerClazz = null; + for (Class clazz : classes) { + if (clazz.getSimpleName().contains("TranslucentConversionListener")) { + translucentConversionListenerClazz = clazz; + } + } + Method method = Activity.class.getDeclaredMethod("convertToTranslucent", + translucentConversionListenerClazz); + method.setAccessible(true); + method.invoke(activity, new Object[] { + null + }); + } catch (Throwable t) { + } + } + + /** + * Calling the convertToTranslucent method on platforms after Android 5.0 + */ + private static void convertActivityToTranslucentAfterL(Activity activity) { + try { + Method getActivityOptions = Activity.class.getDeclaredMethod("getActivityOptions"); + getActivityOptions.setAccessible(true); + Object options = getActivityOptions.invoke(activity); + + Class[] classes = Activity.class.getDeclaredClasses(); + Class translucentConversionListenerClazz = null; + for (Class clazz : classes) { + if (clazz.getSimpleName().contains("TranslucentConversionListener")) { + translucentConversionListenerClazz = clazz; + } + } + Method convertToTranslucent = Activity.class.getDeclaredMethod("convertToTranslucent", + translucentConversionListenerClazz, ActivityOptions.class); + convertToTranslucent.setAccessible(true); + convertToTranslucent.invoke(activity, null, options); + } catch (Throwable t) { + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/activities/swipe/ViewDragHelper.java b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/ViewDragHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..e71fae6c9fe955398f6aec93fa8121012e0fa390 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/activities/swipe/ViewDragHelper.java @@ -0,0 +1,1577 @@ +/* + * Copyright (C) 2013 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 net.oschina.app.improve.base.activities.swipe; + +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.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; + +/** + * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a + * number of useful operations and state tracking for allowing a user to drag + * and reposition views within their parent ViewGroup. + */ +@SuppressWarnings("all") + class ViewDragHelper { + private static final String TAG = "ViewDragHelper"; + + /** + * A null/invalid pointer ID. + */ + static final int INVALID_POINTER = -1; + + /** + * A view is not currently being dragged or animating as a result of a + * fling/snap. + */ + 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; + + public 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[] mInitialEdgeTouched; + + 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(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); + } + + /** + * Sets the sensitivity of the dragger. + * + * @param context The application context. + * @param sensitivity value between 0 and 1, the final value for touchSlop = + * ViewConfiguration.getScaledTouchSlop * (1 / s); + */ + public void setSensitivity(Context context, float sensitivity) { + float s = Math.max(0f, Math.min(1.0f, sensitivity)); + ViewConfiguration viewConfiguration = ViewConfiguration.get(context); + mTouchSlop = (int) (viewConfiguration.getScaledTouchSlop() * (1 / s)); + } + + /** + * 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; + } + + /** + * Set the max 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 maxVel max velocity to detect + */ + public void setMaxVelocity(float maxVel) { + mMaxVelocity = maxVel; + } + + /** + * 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 + *

    + * 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; + } + + /** + * Set 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. + * + * @param size The size of an edge in pixels + */ + public void setEdgeSize(int size) { + mEdgeSize = size; + } + + /** + * Capture a specific child view for dragging within the parent. The + * callback will be notified but + * 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(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; + + return forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0); + } + + /** + * 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 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) { + mCapturedView.offsetLeftAndRight(dx); + } + if (dy != 0) { + mCapturedView.offsetTopAndBottom(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 = mScroller.isFinished(); + } + + 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(mInitialEdgeTouched, 0); + Arrays.fill(mEdgeDragsInProgress, 0); + Arrays.fill(mEdgeDragsLocked, 0); + mPointersDown = 0; + } + + private void clearMotionHistory(int pointerId) { + if (mInitialMotionX == null) { + return; + } + mInitialMotionX[pointerId] = 0; + mInitialMotionY[pointerId] = 0; + mLastMotionX[pointerId] = 0; + mLastMotionY[pointerId] = 0; + mInitialEdgeTouched[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(mInitialEdgeTouched, 0, iit, 0, mInitialEdgeTouched.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; + mInitialEdgeTouched = 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; + mInitialEdgeTouched[pointerId] = getEdgeTouched((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); + 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(MotionEvent)} or + * {@link #processTouchEvent(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) { + if (mDragState != state) { + mDragState = state; + mCallback.onViewDragStateChanged(state); + if (state == 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 = mInitialEdgeTouched[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 = mInitialEdgeTouched[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: { + // 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); + 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 (toCapture != null && checkTouchSlop(toCapture, dx, dy) + && 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 = mInitialEdgeTouched[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 = mInitialEdgeTouched[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) { + 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); + 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 ((mInitialEdgeTouched[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 (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(MotionEvent)} or + * {@link #processTouchEvent(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(MotionEvent)} or + * {@link #processTouchEvent(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 = mInitialEdgeTouched.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) && (mInitialEdgeTouched[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); + mCapturedView.offsetLeftAndRight(clampedX - oldLeft); + } + if (dy != 0) { + clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy); + mCapturedView.offsetTopAndBottom(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 + * . + * + * @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 getEdgeTouched(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; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/adapter/BaseGeneralRecyclerAdapter.java b/app/src/main/java/net/oschina/app/improve/base/adapter/BaseGeneralRecyclerAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..07694f5cf3d66c7be535b0cd667f022cafde2459 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/adapter/BaseGeneralRecyclerAdapter.java @@ -0,0 +1,61 @@ +package net.oschina.app.improve.base.adapter; + +import android.content.Context; + +import com.bumptech.glide.RequestManager; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Created by huanghaibin_dev + * on 2016/8/18. + */ + +public abstract class BaseGeneralRecyclerAdapter extends BaseRecyclerAdapter { + protected Callback mCallBack; + private List mPreItems; + + public BaseGeneralRecyclerAdapter(Callback callback, int mode) { + super(callback.getContext(), mode); + mCallBack = callback; + setState(STATE_LOADING, true); + } + + + @SuppressWarnings("UnusedReturnValue") + public int addItems(List items) { + int filterOut = 0; + if (items != null && !items.isEmpty()) { + List date = new ArrayList<>(); + if (mPreItems != null) { + for (T d : items) { + if (!mPreItems.contains(d)) { + date.add(d); + } else { + filterOut++; + } + } + } else { + date = items; + } + mPreItems = items; + addAll(date); + } + return filterOut; + } + + public void clearPreItems() { + mPreItems = null; + } + + @SuppressWarnings("unused") + public interface Callback { + RequestManager getImgLoader(); + + Context getContext(); + + Date getSystemTime(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/adapter/BaseListAdapter.java b/app/src/main/java/net/oschina/app/improve/base/adapter/BaseListAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..d99ab246569f610ee0216742381681386910a9b2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/adapter/BaseListAdapter.java @@ -0,0 +1,157 @@ +package net.oschina.app.improve.base.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +import com.bumptech.glide.RequestManager; + +import net.oschina.app.adapter.ViewHolder; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 通用的ViewHolder + *

    + * Created by 火蚁 on 15/4/8. + */ +@SuppressWarnings("unused") +public abstract class BaseListAdapter extends BaseAdapter implements ViewHolder.Callback { + protected LayoutInflater mInflater; + private List mDatas; + private List mPreData; + protected Callback mCallback; + + public BaseListAdapter(Callback callback) { + this.mCallback = callback; + this.mInflater = LayoutInflater.from(callback.getContext()); + this.mDatas = new ArrayList(); + } + + @Override + public int getCount() { + return mDatas.size(); + } + + @Override + public T getItem(int position) { + if (position >= 0 && position < mDatas.size()) + return mDatas.get(position); + return null; + } + + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + T time = getItem(position); + int layoutId = getLayoutId(position, time); + final ViewHolder vh = ViewHolder.getViewHolder(this, convertView, parent, layoutId, position); + convert(vh, time, position); + return vh.getConvertView(); + } + + public List getDatas() { + return this.mDatas; + } + + protected abstract void convert(ViewHolder vh, T item, int position); + + protected abstract int getLayoutId(int position, T item); + + public void updateItem(int location, T item) { + if (mDatas.isEmpty()) return; + mDatas.set(location, item); + notifyDataSetChanged(); + } + + public void addItem(T item) { + checkListNull(); + mDatas.add(item); + notifyDataSetChanged(); + } + + public void addItem(int location, T item) { + checkListNull(); + mDatas.add(location, item); + notifyDataSetChanged(); + } + + public void addItem(List items) { + checkListNull(); + if (items != null) { + List date = new ArrayList<>(); + if (mPreData != null) { + for (T d : items) { + if (!mPreData.contains(d)) { + date.add(d); + } + } + } else { + date = items; + } + mPreData = items; + mDatas.addAll(date); + } + notifyDataSetChanged(); + } + + public void addItem(int position, List items) { + checkListNull(); + mDatas.addAll(position, items); + notifyDataSetChanged(); + } + + public void removeItem(int location) { + if (mDatas == null || mDatas.isEmpty()) { + return; + } + mDatas.remove(location); + notifyDataSetChanged(); + } + + public void clear() { + if (mDatas == null || mDatas.isEmpty()) { + return; + } + mPreData = null; + mDatas.clear(); + notifyDataSetChanged(); + } + + public void checkListNull() { + if (mDatas == null) { + mDatas = new ArrayList(); + } + } + + public int getCurrentPage() { + return getCount() % 20; + } + + @Override + public RequestManager getImgLoader() { + return mCallback.getImgLoader(); + } + + @Override + public LayoutInflater getInflate() { + return mInflater; + } + + public interface Callback { + RequestManager getImgLoader(); + + Context getContext(); + + Date getSystemTime(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/adapter/BaseRecyclerAdapter.java b/app/src/main/java/net/oschina/app/improve/base/adapter/BaseRecyclerAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..45436becd58db5973db2d70ce744f2c1a7cceb27 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/adapter/BaseRecyclerAdapter.java @@ -0,0 +1,480 @@ +package net.oschina.app.improve.base.adapter; + +import android.content.Context; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; + +import net.oschina.app.R; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * the base adapter for RecyclerView + * Created by huanghaibin on 16-5-3. + */ +@SuppressWarnings("unused") +public abstract class BaseRecyclerAdapter extends RecyclerView.Adapter { + protected List mItems; + protected Context mContext; + protected LayoutInflater mInflater; + + protected String mSystemTime; + + protected int mSelectedPosition = -0; + public static final int STATE_NO_MORE = 1; + public static final int STATE_LOAD_MORE = 2; + public static final int STATE_INVALID_NETWORK = 3; + public static final int STATE_HIDE = 5; + private static final int STATE_REFRESHING = 6; + public static final int STATE_LOAD_ERROR = 7; + public static final int STATE_LOADING = 8; + public static final int STATE_LOAD = 9; + + private final int BEHAVIOR_MODE; + protected int mState; + + public static final int NEITHER = 0; + public static final int ONLY_HEADER = 1; + public static final int ONLY_FOOTER = 2; + public static final int BOTH_HEADER_FOOTER = 3; + + protected static final int VIEW_TYPE_NORMAL = 0; + private static final int VIEW_TYPE_HEADER = -1; + private static final int VIEW_TYPE_FOOTER = -2; + + private OnItemClickListener onItemClickListener; + private OnItemLongClickListener onItemLongClickListener; + + private OnClickListener onClickListener; + private OnLongClickListener onLongClickListener; + + + protected View mHeaderView; + + private OnLoadingHeaderCallBack onLoadingHeaderCallBack; + + public BaseRecyclerAdapter(Context context, int mode) { + mItems = new ArrayList<>(); + this.mContext = context; + this.mInflater = LayoutInflater.from(context); + BEHAVIOR_MODE = mode; + mState = STATE_HIDE; + //mFooterView = mInflater.inflate(R.layout.footer_view, null); + initListener(); + } + + /** + * 初始化listener + */ + private void initListener() { + onClickListener = new OnClickListener() { + @Override + public void onClick(int position, long itemId) { + if (onItemClickListener != null) + onItemClickListener.onItemClick(position, itemId); + } + }; + + onLongClickListener = new OnLongClickListener() { + @Override + public boolean onLongClick(int position, long itemId) { + if (onItemLongClickListener != null) { + onItemLongClickListener.onLongClick(position, itemId); + return true; + } + return false; + } + }; + } + + public void setSystemTime(String systemTime) { + this.mSystemTime = systemTime; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + switch (viewType) { + case VIEW_TYPE_HEADER: + if (onLoadingHeaderCallBack != null) + return onLoadingHeaderCallBack.onCreateHeaderHolder(parent); + else + throw new IllegalArgumentException("you have to impl the interface when using this viewType"); + case VIEW_TYPE_FOOTER: + return new FooterViewHolder(mInflater.inflate(R.layout.recycler_footer_view, parent, false)); + default: + final RecyclerView.ViewHolder holder = onCreateDefaultViewHolder(parent, viewType); + if (holder != null) { + holder.itemView.setTag(holder); + holder.itemView.setOnLongClickListener(onLongClickListener); + holder.itemView.setOnClickListener(onClickListener); + onBindClickListener(holder); + } + return holder; + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case VIEW_TYPE_HEADER: + if (onLoadingHeaderCallBack != null) + onLoadingHeaderCallBack.onBindHeaderHolder(holder, position); + break; + case VIEW_TYPE_FOOTER: + FooterViewHolder fvh = (FooterViewHolder) holder; + fvh.itemView.setVisibility(View.VISIBLE); + switch (mState) { + case STATE_INVALID_NETWORK: + fvh.tv_footer.setText(mContext.getResources().getString(R.string.state_network_error)); + fvh.pb_footer.setVisibility(View.GONE); + break; + case STATE_LOAD_MORE: + fvh.tv_footer.setText(mContext.getResources().getString(R.string.state_load)); + fvh.pb_footer.setVisibility(View.GONE); + break; + case STATE_LOADING: + fvh.tv_footer.setText(mContext.getResources().getString(R.string.state_loading)); + fvh.pb_footer.setVisibility(View.VISIBLE); + break; + case STATE_NO_MORE: + fvh.tv_footer.setText(mContext.getResources().getString(R.string.state_not_more)); + fvh.pb_footer.setVisibility(View.GONE); + break; + case STATE_REFRESHING: + fvh.tv_footer.setText(mContext.getResources().getString(R.string.state_refreshing)); + fvh.pb_footer.setVisibility(View.GONE); + break; + case STATE_LOAD_ERROR: + fvh.tv_footer.setText(mContext.getResources().getString(R.string.state_load_error)); + fvh.pb_footer.setVisibility(View.GONE); + break; + case STATE_LOAD: + fvh.tv_footer.setText(mContext.getResources().getString(R.string.state_load)); + fvh.pb_footer.setVisibility(View.GONE); + break; + case STATE_HIDE: + fvh.itemView.setVisibility(View.GONE); + break; + } + break; + default: + onBindDefaultViewHolder(holder, getItems().get(getIndex(position)), position); + break; + } + } + + protected void onBindClickListener(RecyclerView.ViewHolder holder) { + + } + + /** + * 当添加到RecyclerView时获取GridLayoutManager布局管理器,修正header和footer显示整行 + * + * @param recyclerView the mRecyclerView + */ + @Override + public void onAttachedToRecyclerView(RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); + if (manager instanceof GridLayoutManager) { + final GridLayoutManager gridManager = ((GridLayoutManager) manager); + gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + return getItemViewType(position) == VIEW_TYPE_HEADER || getItemViewType(position) == VIEW_TYPE_FOOTER + ? gridManager.getSpanCount() : 1; + } + }); + } + } + + /** + * 当RecyclerView在windows活动时获取StaggeredGridLayoutManager布局管理器,修正header和footer显示整行 + * + * @param holder the RecyclerView.ViewHolder + */ + @Override + public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { + ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); + if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) { + StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; + if (BEHAVIOR_MODE == ONLY_HEADER) { + p.setFullSpan(holder.getLayoutPosition() == 0); + } else if (BEHAVIOR_MODE == ONLY_FOOTER) { + p.setFullSpan(holder.getLayoutPosition() == mItems.size() + 1); + } else if (BEHAVIOR_MODE == BOTH_HEADER_FOOTER) { + if (holder.getLayoutPosition() == 0 || holder.getLayoutPosition() == mItems.size() + 1) { + p.setFullSpan(true); + } + } + } + } + + @Override + public int getItemViewType(int position) { + if (position == 0 && (BEHAVIOR_MODE == ONLY_HEADER || BEHAVIOR_MODE == BOTH_HEADER_FOOTER)) + return VIEW_TYPE_HEADER; + if (position + 1 == getItemCount() && (BEHAVIOR_MODE == ONLY_FOOTER || BEHAVIOR_MODE == BOTH_HEADER_FOOTER)) + return VIEW_TYPE_FOOTER; + else return VIEW_TYPE_NORMAL; + } + + + public T getSelectedItem() { + if (mSelectedPosition < 0 || mSelectedPosition >= mItems.size()) + return null; + return mItems.get(mSelectedPosition); + } + + + /** + * 单选 + */ + public void setSelectedPosition(int selectedPosition) { + if (selectedPosition != mSelectedPosition) { + updateItem(mSelectedPosition); + mSelectedPosition = selectedPosition; + updateItem(mSelectedPosition); + } + this.mSelectedPosition = selectedPosition; + updateItem(selectedPosition); + } + + protected int getIndex(int position) { + return BEHAVIOR_MODE == ONLY_HEADER || BEHAVIOR_MODE == BOTH_HEADER_FOOTER ? position - 1 : position; + } + + @Override + public int getItemCount() { + if (BEHAVIOR_MODE == ONLY_FOOTER || BEHAVIOR_MODE == ONLY_HEADER) { + return mItems.size() + 1; + } else if (BEHAVIOR_MODE == BOTH_HEADER_FOOTER) { + return mItems.size() + 2; + } else return mItems.size(); + } + + public int getCount() { + return mItems.size(); + } + + protected abstract RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type); + + protected abstract void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, T item, int position); + + public final View getHeaderView() { + return this.mHeaderView; + } + + public final void setHeaderView(View view) { + this.mHeaderView = view; + } + + public final List getItems() { + return mItems; + } + + + public void addAll(List items) { + if (items != null) { + this.mItems.addAll(items); + notifyItemRangeInserted(this.mItems.size(), items.size()); + } + } + + public final void addItem(T item) { + if (item != null) { + this.mItems.add(item); + notifyItemChanged(mItems.size()); + } + } + + + public void addItem(int position, T item) { + if (item != null) { + this.mItems.add(getIndex(position), item); + notifyItemInserted(position); + } + } + + public void replaceItem(int position, T item) { + if (item != null) { + this.mItems.set(getIndex(position), item); + notifyItemChanged(position); + } + } + + public void updateItem(int position) { + if (getItemCount() > position) { + notifyItemChanged(position); + } + } + + + public final void removeItem(T item) { + if (this.mItems.contains(item)) { + int position = mItems.indexOf(item); + this.mItems.remove(item); + notifyItemRemoved(position); + } + } + + public final void removeItem(int position) { + if (this.getItemCount() > position) { + this.mItems.remove(getIndex(position)); + notifyItemRemoved(position); + } + } + + public void moveItem(int fromPosition, int toPosition) { + if (fromPosition < toPosition) { + for (int i = fromPosition; i < toPosition; i++) { + Collections.swap(mItems, i, i + 1); + } + } else { + for (int i = fromPosition; i > toPosition; i--) { + Collections.swap(mItems, i, i - 1); + } + } + notifyItemMoved(fromPosition, toPosition); + } + + public final T getItem(int position) { + int p = getIndex(position); + if (p < 0 || p >= mItems.size()) + return null; + return mItems.get(getIndex(position)); + } + + public final void resetItem(List items) { + if (items != null) { + clear(); + addAll(items); + } + } + + public final void clear() { + this.mItems.clear(); + setState(STATE_HIDE, false); + notifyDataSetChanged(); + } + + public void setState(int mState, boolean isUpdate) { + this.mState = mState; + if (isUpdate) + updateItem(getItemCount() - 1); + } + + public int getState() { + return mState; + } + + /** + * 添加项点击事件 + * + * @param onItemClickListener the RecyclerView item click listener + */ + public void setOnItemClickListener(OnItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } + + /** + * 添加项点长击事件 + * + * @param onItemLongClickListener the RecyclerView item long click listener + */ + public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) { + this.onItemLongClickListener = onItemLongClickListener; + } + + protected void setOnLoadingHeaderCallBack(OnLoadingHeaderCallBack listener) { + onLoadingHeaderCallBack = listener; + } + + + /** + * 可以共用同一个listener,相对高效 + */ + public static abstract class OnClickListener implements View.OnClickListener { + @Override + public void onClick(View v) { + RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder) v.getTag(); + onClick(holder.getAdapterPosition(), holder.getItemId()); + } + + public abstract void onClick(int position, long itemId); + } + + /** + * 点击事件 + */ + public static abstract class OnViewClickListener implements View.OnClickListener { + @Override + public void onClick(View v) { + RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder) v.getTag(); + onClick(v, holder.getAdapterPosition()); + } + + public abstract void onClick(View view, int position); + } + + + /** + * 可以共用同一个listener,相对高效 + */ + public static abstract class OnLongClickListener implements View.OnLongClickListener { + @Override + public boolean onLongClick(View v) { + RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder) v.getTag(); + return onLongClick(holder.getAdapterPosition(), holder.getItemId()); + } + + public abstract boolean onLongClick(int position, long itemId); + } + + + /** + * + */ + public interface OnItemClickListener { + void onItemClick(int position, long itemId); + } + + + public interface OnItemLongClickListener { + void onLongClick(int position, long itemId); + } + + /** + * for load header view + */ + public interface OnLoadingHeaderCallBack { + RecyclerView.ViewHolder onCreateHeaderHolder(ViewGroup parent); + + void onBindHeaderHolder(RecyclerView.ViewHolder holder, int position); + } + + public static class FooterViewHolder extends RecyclerView.ViewHolder { + ProgressBar pb_footer; + TextView tv_footer; + + FooterViewHolder(View view) { + super(view); + pb_footer = (ProgressBar) view.findViewById(R.id.pb_footer); + tv_footer = (TextView) view.findViewById(R.id.tv_footer); + } + } + + public class HeaderViewHolder extends RecyclerView.ViewHolder { + public HeaderViewHolder(View itemView) { + super(itemView); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/fragments/BaseFragment.java b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..4e5118559629ec6b7aeb3d000fabd4aac7c1d18e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseFragment.java @@ -0,0 +1,239 @@ +package net.oschina.app.improve.base.fragments; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.text.TextUtils; +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.bumptech.glide.RequestManager; + +import net.oschina.app.util.ImageLoader; + +import java.io.Serializable; + +import butterknife.ButterKnife; + +/** + * Fragment基础类 + */ + +@SuppressWarnings("WeakerAccess") +public abstract class BaseFragment extends Fragment { + protected Context mContext; + protected View mRoot; + protected Bundle mBundle; + private RequestManager mImgLoader; + protected LayoutInflater mInflater; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mContext = context; + } + + @Override + public void onDetach() { + super.onDetach(); + mContext = null; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mBundle = getArguments(); + initBundle(mBundle); + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + if (mRoot != null) { + ViewGroup parent = (ViewGroup) mRoot.getParent(); + if (parent != null) + parent.removeView(mRoot); + } else { + mRoot = inflater.inflate(getLayoutId(), container, false); + mInflater = inflater; + // Do something + onBindViewBefore(mRoot); + // Bind view + ButterKnife.bind(this, mRoot); + // Get savedInstanceState + if (savedInstanceState != null) + onRestartInstance(savedInstanceState); + // Init + initWidget(mRoot); + initData(); + } + return mRoot; + } + + protected void onBindViewBefore(View root) { + // ... + } + + @Override + public void onDestroy() { + super.onDestroy(); + ButterKnife.unbind(this); + mImgLoader = null; + mBundle = null; + } + + protected abstract int getLayoutId(); + + protected void initBundle(Bundle bundle) { + + } + + protected void initWidget(View root) { + + } + + protected void initData() { + + } + + protected T findView(int viewId) { + return (T) mRoot.findViewById(viewId); + } + + protected T getBundleSerializable(String key) { + if (mBundle == null) { + return null; + } + return (T) mBundle.getSerializable(key); + } + + /** + * 获取一个图片加载管理器 + * + * @return RequestManager + */ + public synchronized RequestManager getImgLoader() { + if (mImgLoader == null) + mImgLoader = Glide.with(this); + return mImgLoader; + } + + /*** + * 从网络中加载数据 + * + * @param viewId view的id + * @param imageUrl 图片地址 + */ + protected void setImageFromNet(int viewId, String imageUrl) { + setImageFromNet(viewId, imageUrl, 0); + } + + /*** + * 从网络中加载数据 + * + * @param viewId view的id + * @param imageUrl 图片地址 + * @param placeholder 图片地址为空时的资源 + */ + protected void setImageFromNet(int viewId, String imageUrl, int placeholder) { + ImageView imageView = findView(viewId); + setImageFromNet(imageView, imageUrl, placeholder); + } + + /*** + * 从网络中加载数据 + * + * @param imageView imageView + * @param imageUrl 图片地址 + */ + protected void setImageFromNet(ImageView imageView, String imageUrl) { + setImageFromNet(imageView, imageUrl, 0); + } + + /*** + * 从网络中加载数据 + * + * @param imageView imageView + * @param imageUrl 图片地址 + * @param placeholder 图片地址为空时的资源 + */ + protected void setImageFromNet(ImageView imageView, String imageUrl, int placeholder) { + ImageLoader.loadImage(getImgLoader(), imageView, imageUrl, placeholder); + } + + + protected void setText(int viewId, String text) { + TextView textView = findView(viewId); + if (TextUtils.isEmpty(text)) { + return; + } + textView.setText(text); + } + + protected void setText(int viewId, String text, String emptyTip) { + TextView textView = findView(viewId); + if (TextUtils.isEmpty(text)) { + textView.setText(emptyTip); + return; + } + textView.setText(text); + } + + protected void setTextEmptyGone(int viewId, String text) { + TextView textView = findView(viewId); + if (TextUtils.isEmpty(text)) { + textView.setVisibility(View.GONE); + return; + } + textView.setText(text); + } + + protected T setGone(int id) { + T view = findView(id); + view.setVisibility(View.GONE); + return view; + } + + protected T setVisibility(int id) { + T view = findView(id); + view.setVisibility(View.VISIBLE); + return view; + } + + protected void setInVisibility(int id) { + findView(id).setVisibility(View.INVISIBLE); + } + + protected void onRestartInstance(Bundle bundle) { + + } + + protected void setStatusBarPadding() { + mRoot.setPadding(0, getStatusHeight(mContext), 0, 0); + } + + @SuppressLint("ObsoleteSdkInt,PrivateApi") + private static int getStatusHeight(Context context) { + int statusHeight = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + try { + Class clazz = Class.forName("com.android.internal.R$dimen"); + Object object = clazz.newInstance(); + int height = Integer.parseInt(clazz.getField("status_bar_height") + .get(object).toString()); + statusHeight = context.getResources().getDimensionPixelSize(height); + } catch (Exception e) { + e.printStackTrace(); + } + } + return statusHeight; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/fragments/BaseGeneralListFragment.java b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseGeneralListFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..0dbb1799f6450da1bffc4bdc10685de113646514 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseGeneralListFragment.java @@ -0,0 +1,19 @@ +package net.oschina.app.improve.base.fragments; + +import net.oschina.app.interf.OnTabReselectListener; + +/** + * Created by JuQiu + * on 16/6/6. + */ + +public abstract class BaseGeneralListFragment extends BaseListFragment implements OnTabReselectListener { + @Override + public void onTabReselect() { + if (mListView != null) { + mListView.setSelection(0); + mRefreshLayout.setRefreshing(true); + onRefreshing(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/fragments/BaseGeneralRecyclerFragment.java b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseGeneralRecyclerFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..2da7430e16028065e6eeeb63f1a8955f026fc250 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseGeneralRecyclerFragment.java @@ -0,0 +1,18 @@ +package net.oschina.app.improve.base.fragments; + +import net.oschina.app.interf.OnTabReselectListener; + +/** + * Created by huanghaibin_dev + * on 2016/8/30. + */ +public abstract class BaseGeneralRecyclerFragment extends BaseRecyclerViewFragment implements OnTabReselectListener { + @Override + public void onTabReselect() { + if (mRecyclerView != null && !isRefreshing) { + mRecyclerView.scrollToPosition(0); + mRefreshLayout.setRefreshing(true); + onRefreshing(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/fragments/BaseListFragment.java b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseListFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..d9ba78949ea9d79eaba4c583ba8822aefb1ad454 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseListFragment.java @@ -0,0 +1,262 @@ +package net.oschina.app.improve.base.fragments; + +import android.view.LayoutInflater; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.cache.CacheManager; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.adapter.BaseListAdapter; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.widget.SuperRefreshLayout; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Date; + +import cz.msebera.android.httpclient.Header; + +/** + * T as the base bean + * Created by huanghaibin + * on 16-5-23. + */ +public abstract class BaseListFragment extends BaseFragment implements + SuperRefreshLayout.SuperRefreshLayoutListener, + AdapterView.OnItemClickListener, BaseListAdapter.Callback, + View.OnClickListener { + + public static final int TYPE_NORMAL = 0; + public static final int TYPE_LOADING = 1; + public static final int TYPE_NO_MORE = 2; + public static final int TYPE_ERROR = 3; + public static final int TYPE_NET_ERROR = 4; + protected String CACHE_NAME = getClass().getName(); + protected ListView mListView; + protected SuperRefreshLayout mRefreshLayout; + protected EmptyLayout mErrorLayout; + protected BaseListAdapter mAdapter; + protected boolean mIsRefresh; + protected TextHttpResponseHandler mHandler; + protected PageBean mBean; + private String mTime; + private View mFooterView; + private ProgressBar mFooterProgressBar; + private TextView mFooterText; + + @Override + protected int getLayoutId() { + return R.layout.fragment_base_list; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mListView = (ListView) root.findViewById(R.id.listView); + mRefreshLayout = (SuperRefreshLayout) root.findViewById(R.id.superRefreshLayout); + mRefreshLayout.setColorSchemeResources( + R.color.swiperefresh_color1, R.color.swiperefresh_color2, + R.color.swiperefresh_color3, R.color.swiperefresh_color4); + mErrorLayout = (EmptyLayout) root.findViewById(R.id.error_layout); + mRefreshLayout.setSuperRefreshLayoutListener(this); + mFooterView = LayoutInflater.from(getContext()).inflate(R.layout.layout_list_view_footer, null); + mFooterText = (TextView) mFooterView.findViewById(R.id.tv_footer); + mFooterProgressBar = (ProgressBar) mFooterView.findViewById(R.id.pb_footer); + mListView.setOnItemClickListener(this); + + mErrorLayout.setOnLayoutClickListener(this); + if (isNeedFooter()) + mListView.addFooterView(mFooterView); + } + + @Override + protected void initData() { + super.initData(); + //when open this fragment,read the obj + + mAdapter = getListAdapter(); + mListView.setAdapter(mAdapter); + + mHandler = new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + onRequestError(statusCode); + onRequestFinish(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> resultBean = AppOperator.createGson().fromJson(responseString, getType()); + if (resultBean != null && resultBean.isSuccess() && resultBean.getResult().getItems() != null) { + onRequestSuccess(resultBean.getCode()); + setListData(resultBean); + } else { + setFooterType(TYPE_NO_MORE); + //mRefreshLayout.setNoMoreData(); + } + onRequestFinish(); + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + }; + + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + mBean = (PageBean) CacheManager.readObject(getActivity(), CACHE_NAME); + //if is the first loading + if (mBean == null) { + mBean = new PageBean<>(); + mBean.setItems(new ArrayList()); + onRefreshing(); + } else { + mRoot.post(new Runnable() { + @Override + public void run() { + mAdapter.addItem(mBean.getItems()); + mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + mRefreshLayout.setVisibility(View.VISIBLE); + onRefreshing(); + } + }); + } + } + }); + } + + @Override + public void onClick(View v) { + mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + onRefreshing(); + } + + @Override + public void onRefreshing() { + mIsRefresh = true; + requestData(); + } + + @Override + public void onLoadMore() { + requestData(); + } + + /** + * request network data + */ + protected void requestData() { + onRequestStart(); + setFooterType(TYPE_LOADING); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + + } + + protected void onRequestStart() { + + } + + protected void onRequestSuccess(int code) { + + } + + protected void onRequestError(int code) { + setFooterType(TYPE_NET_ERROR); + if (mAdapter.getDatas().size() == 0) + mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); + } + + protected void onRequestFinish() { + onComplete(); + } + + protected void onComplete() { + mRefreshLayout.onLoadComplete(); + mIsRefresh = false; + } + + protected void setListData(ResultBean> resultBean) { + //is refresh + mBean.setNextPageToken(resultBean.getResult().getNextPageToken()); + if (mIsRefresh) { + //cache the time + mTime = resultBean.getTime(); + mBean.setItems(resultBean.getResult().getItems()); + mAdapter.clear(); + mAdapter.addItem(mBean.getItems()); + mBean.setPrevPageToken(resultBean.getResult().getPrevPageToken()); + mRefreshLayout.setCanLoadMore(); + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + CacheManager.saveObject(getActivity(), mBean, CACHE_NAME); + } + }); + } else { + mAdapter.addItem(resultBean.getResult().getItems()); + } + if (resultBean.getResult().getItems().size() < 20) { + setFooterType(TYPE_NO_MORE); + //mRefreshLayout.setNoMoreData(); + } + if (mAdapter.getDatas().size() > 0) { + mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + mRefreshLayout.setVisibility(View.VISIBLE); + } else { + mErrorLayout.setErrorType(EmptyLayout.NODATA); + } + } + + @Override + public Date getSystemTime() { + return new Date(); + } + + protected abstract BaseListAdapter getListAdapter(); + + protected abstract Type getType(); + + protected boolean isNeedFooter() { + return true; + } + + + protected void setFooterType(int type) { + try { + switch (type) { + case TYPE_NORMAL: + case TYPE_LOADING: + mFooterText.setText(getResources().getString(R.string.footer_type_loading)); + mFooterProgressBar.setVisibility(View.VISIBLE); + break; + case TYPE_NET_ERROR: + mFooterText.setText(getResources().getString(R.string.footer_type_net_error)); + mFooterProgressBar.setVisibility(View.GONE); + break; + case TYPE_ERROR: + mFooterText.setText(getResources().getString(R.string.footer_type_error)); + mFooterProgressBar.setVisibility(View.GONE); + break; + case TYPE_NO_MORE: + mFooterText.setText(getResources().getString(R.string.footer_type_not_more)); + mFooterProgressBar.setVisibility(View.GONE); + break; + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/fragments/BasePagerFragment.java b/app/src/main/java/net/oschina/app/improve/base/fragments/BasePagerFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..0bed78a091acca51076007791f2a4cb6df5af4b0 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/fragments/BasePagerFragment.java @@ -0,0 +1,105 @@ +package net.oschina.app.improve.base.fragments; + +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.view.View; +import android.view.ViewGroup; + +import net.oschina.app.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tab Fragment + * Created by huanghaibin on 2017/10/23. + */ + +public abstract class BasePagerFragment extends BaseFragment { + protected TabLayout mTabLayout; + protected ViewPager mViewPager; + protected Adapter mAdapter; + + @Override + protected int getLayoutId() { + return R.layout.fragment_base_view_pager; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mTabLayout = (TabLayout) root.findViewById(R.id.tabLayout); + mViewPager = (ViewPager) root.findViewById(R.id.viewPager); + mAdapter = new Adapter(getChildFragmentManager()); + mAdapter.reset(getFragments()); + mAdapter.reset(getTitles()); + mViewPager.setAdapter(mAdapter); + if (mTabLayout != null) { + mTabLayout.setupWithViewPager(mViewPager); + setupTabView(); + } + + } + + protected void setupTabView() { + + } + + @Override + protected void initData() { + + } + + public static class Adapter extends FragmentPagerAdapter { + private List mFragment = new ArrayList<>(); + private Fragment mCurFragment; + private String[] mTitles; + + Adapter(FragmentManager fm) { + super(fm); + } + + void reset(List fragments) { + mFragment.clear(); + mFragment.addAll(fragments); + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + super.setPrimaryItem(container, position, object); + if (object instanceof Fragment) { + mCurFragment = (Fragment) object; + } + } + + public Fragment getCurFragment() { + return mCurFragment; + } + + void reset(String[] titles) { + this.mTitles = titles; + } + + @Override + public Fragment getItem(int position) { + return mFragment.get(position); + } + + @Override + public int getCount() { + return mFragment.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + return mTitles[position]; + } + } + + protected abstract List getFragments(); + + protected abstract String[] getTitles(); +} diff --git a/app/src/main/java/net/oschina/app/improve/base/fragments/BaseRecyclerViewFragment.java b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseRecyclerViewFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..9be4865cae84d936e1cfff1e5adf5600ab8e815c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseRecyclerViewFragment.java @@ -0,0 +1,310 @@ +package net.oschina.app.improve.base.fragments; + +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppConfig; +import net.oschina.app.R; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.utils.CacheManager; +import net.oschina.app.improve.widget.RecyclerRefreshLayout; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.TLog; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + + +/** + * 基本列表类,重写getLayoutId()自定义界面 + * Created by huanghaibin_dev + * on 2016/4/12. + */ +public abstract class BaseRecyclerViewFragment extends BaseFragment implements + RecyclerRefreshLayout.SuperRefreshLayoutListener, + BaseRecyclerAdapter.OnItemClickListener, + View.OnClickListener, + BaseGeneralRecyclerAdapter.Callback { + private final String TAG = this.getClass().getSimpleName(); + protected BaseRecyclerAdapter mAdapter; + protected RecyclerView mRecyclerView; + protected RecyclerRefreshLayout mRefreshLayout; + protected boolean isRefreshing; + protected TextHttpResponseHandler mHandler; + protected PageBean mBean; + protected String CACHE_NAME = getClass().getName(); + protected EmptyLayout mErrorLayout; + + @Override + public int getLayoutId() { + return R.layout.fragment_base_recycler_view; + } + + @Override + protected void initWidget(View root) { + mRecyclerView = (RecyclerView) root.findViewById(R.id.recyclerView); + mRefreshLayout = (RecyclerRefreshLayout) root.findViewById(R.id.refreshLayout); + mErrorLayout = (EmptyLayout) root.findViewById(R.id.error_layout); + } + + @SuppressWarnings("unchecked") + @Override + public void initData() { + mBean = new PageBean<>(); + mAdapter = getRecyclerAdapter(); + mAdapter.setState(BaseRecyclerAdapter.STATE_HIDE, false); + mRecyclerView.setAdapter(mAdapter); + mAdapter.setOnItemClickListener(this); + mErrorLayout.setOnLayoutClickListener(this); + mRefreshLayout.setSuperRefreshLayoutListener(this); + mAdapter.setState(BaseRecyclerAdapter.STATE_HIDE, false); + mRecyclerView.setLayoutManager(getLayoutManager()); + mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + if (RecyclerView.SCROLL_STATE_DRAGGING == newState && getActivity() != null + && getActivity().getCurrentFocus() != null) { + TDevice.hideSoftKeyboard(getActivity().getCurrentFocus()); + } + } + }); + mRefreshLayout.setColorSchemeResources( + R.color.swiperefresh_color1, R.color.swiperefresh_color2, + R.color.swiperefresh_color3, R.color.swiperefresh_color4); + + + mHandler = new TextHttpResponseHandler() { + @Override + public void onStart() { + super.onStart(); + log("HttpResponseHandler:onStart"); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + onRequestError(); + log("HttpResponseHandler:onFailure responseString:" + responseString); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + log("HttpResponseHandler:onSuccess responseString:" + responseString); + try { + ResultBean> resultBean = AppOperator.createGson().fromJson(responseString, getType()); + if (resultBean != null && resultBean.isSuccess() && resultBean.getResult().getItems() != null) { + setListData(resultBean); + onRequestSuccess(resultBean.getCode()); + } else { + if (resultBean.getCode() == ResultBean.RESULT_TOKEN_ERROR) { + SimplexToast.show(getActivity(), resultBean.getMessage()); + } + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + + @Override + public void onFinish() { + super.onFinish(); + onRequestFinish(); + log("HttpResponseHandler:onFinish"); + } + + @Override + public void onCancel() { + super.onCancel(); + onRequestFinish(); + } + }; + + boolean isNeedEmptyView = isNeedEmptyView(); + if (isNeedEmptyView) { + mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + mRefreshLayout.setVisibility(View.GONE); + mBean = new PageBean<>(); + + List items = isNeedCache() + ? (List) CacheManager.readListJson(getActivity(), CACHE_NAME, getCacheClass()) + : null; + + mBean.setItems(items); + //if is the first loading + if (items == null) { + mBean.setItems(new ArrayList()); + onRefreshing(); + } else { + mAdapter.addAll(mBean.getItems()); + mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + mRefreshLayout.setVisibility(View.VISIBLE); + mRoot.post(new Runnable() { + @Override + public void run() { + mRefreshLayout.setRefreshing(true); + onRefreshing(); + } + }); + } + } else { + mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + mRefreshLayout.setVisibility(View.VISIBLE); + mRefreshLayout.post(new Runnable() { + @Override + public void run() { + mRefreshLayout.setRefreshing(true); + onRefreshing(); + } + }); + } + } + + @Override + public void onClick(View v) { + mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + onRefreshing(); + } + + + @Override + public void onItemClick(int position, long itemId) { + + } + + @Override + public void onRefreshing() { + isRefreshing = true; + mAdapter.setState(BaseRecyclerAdapter.STATE_HIDE, true); + requestData(); + } + + @Override + public void onLoadMore() { + mAdapter.setState(isRefreshing ? BaseRecyclerAdapter.STATE_HIDE : BaseRecyclerAdapter.STATE_LOADING, true); + requestData(); + } + + @Override + public void onScrollToBottom() { + + } + + protected void requestData() { + } + + protected void onRequestSuccess(int code) { + + } + + protected void onRequestFinish() { + onComplete(); + } + + protected void onRequestError() { + onComplete(); + if (mAdapter.getItems().size() == 0) { + if (isNeedEmptyView()) mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); + mAdapter.setState(BaseRecyclerAdapter.STATE_LOAD_ERROR, true); + } + } + + protected void onComplete() { + mRefreshLayout.onComplete(); + isRefreshing = false; + } + + protected void setListData(ResultBean> resultBean) { + mBean.setNextPageToken(resultBean.getResult().getNextPageToken()); + if (isRefreshing) { + AppConfig.getAppConfig(getActivity()).set("system_time", resultBean.getTime()); + mBean.setItems(resultBean.getResult().getItems()); + mAdapter.clear(); + mAdapter.addAll(mBean.getItems()); + mBean.setPrevPageToken(resultBean.getResult().getPrevPageToken()); + mRefreshLayout.setCanLoadMore(true); + if (isNeedCache()) { + CacheManager.saveToJson(getActivity(), CACHE_NAME, mBean.getItems()); + } + } else { + mAdapter.addAll(resultBean.getResult().getItems()); + } + + if (resultBean.getResult().getItems() == null + || resultBean.getResult().getItems().size() < 20) + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); +// mAdapter.setState(resultBean.getResult().getItems() == null +// || resultBean.getResult().getItems().size() < 20 +// ? BaseRecyclerAdapter.STATE_NO_MORE +// : BaseRecyclerAdapter.STATE_LOADING, true); + + if (mAdapter.getItems().size() > 0) { + mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + mRefreshLayout.setVisibility(View.VISIBLE); + mRecyclerView.setVisibility(View.VISIBLE); + } else { + mErrorLayout.setErrorType( + isNeedEmptyView() + ? EmptyLayout.NODATA + : EmptyLayout.HIDE_LAYOUT); + } + } + + protected RecyclerView.LayoutManager getLayoutManager() { + return new LinearLayoutManager(getActivity()); + } + + protected abstract BaseRecyclerAdapter getRecyclerAdapter(); + + protected abstract Type getType(); + + /** + * 获取缓存bean的class + */ + protected Class getCacheClass() { + return null; + } + + @Override + public Date getSystemTime() { + return new Date(); + } + + /** + * 需要缓存 + * + * @return isNeedCache + */ + protected boolean isNeedCache() { + return true; + } + + /** + * 需要空的View + * + * @return isNeedEmptyView + */ + protected boolean isNeedEmptyView() { + return true; + } + + @SuppressWarnings("ConstantConditions") + private void log(String msg) { + if (false) + TLog.i(TAG, msg); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/fragments/BaseTitleFragment.java b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseTitleFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..0d88e9dc3940cd06fd381c1eedb8968e4f8840c8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseTitleFragment.java @@ -0,0 +1,63 @@ +package net.oschina.app.improve.base.fragments; + +import android.support.annotation.DrawableRes; +import android.support.annotation.LayoutRes; +import android.support.annotation.StringRes; +import android.view.View; +import android.view.ViewStub; + +import net.oschina.app.R; +import net.oschina.app.improve.widget.TitleBar; + +/** + * Created by JuQiu + * on 16/9/5. + */ +public abstract class BaseTitleFragment extends BaseFragment { + + protected TitleBar mTitleBar; + + @Override + protected int getLayoutId() { + return R.layout.fragment_base_title; + } + + @Override + protected void onBindViewBefore(View root) { + super.onBindViewBefore(root); + // on before onBindViewBefore call + ViewStub stub = (ViewStub) root.findViewById(R.id.lay_content); + stub.setLayoutResource(getContentLayoutId()); + stub.inflate(); + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + // not null + mTitleBar = (TitleBar) root.findViewById(R.id.nav_title_bar); + if (mTitleBar != null) { + mTitleBar.setTitle(getTitleRes()); + mTitleBar.setIcon(getIconRes()); + mTitleBar.setIconOnClickListener(getIconClickListener()); + } + } + + protected abstract + @LayoutRes + int getContentLayoutId(); + + protected abstract + @StringRes + int getTitleRes(); + + protected + @DrawableRes + int getIconRes() { + return 0; + } + + protected View.OnClickListener getIconClickListener() { + return null; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/base/fragments/BaseViewPagerFragment.java b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseViewPagerFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..ec2780ef74334d74af74a0540282cfe098034297 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/base/fragments/BaseViewPagerFragment.java @@ -0,0 +1,102 @@ +package net.oschina.app.improve.base.fragments; + +import android.os.Bundle; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.view.View; +import android.view.ViewGroup; + +import net.oschina.app.R; + +import butterknife.Bind; + +/** + * Created by fei + * on 2016/9/5. + *

    + * Changed qiujuer + * on 2016/9/5. + */ +public abstract class BaseViewPagerFragment extends BaseTitleFragment { + + @Bind(R.id.tab_nav) + protected TabLayout mTabNav; + + @Bind(R.id.base_viewPager) + protected ViewPager mBaseViewPager; + + @Override + protected int getContentLayoutId() { + return R.layout.fragment_base_viewpager; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + BaseViewPagerAdapter adapter = new BaseViewPagerAdapter(getChildFragmentManager(), getPagers()); + mBaseViewPager.setAdapter(adapter); + mTabNav.setupWithViewPager(mBaseViewPager); + mBaseViewPager.setCurrentItem(0, true); + } + + protected abstract PagerInfo[] getPagers(); + + public static class PagerInfo { + private String title; + private Class clx; + private Bundle args; + + public PagerInfo(String title, Class clx, Bundle args) { + this.title = title; + this.clx = clx; + this.args = args; + } + } + + public class BaseViewPagerAdapter extends FragmentPagerAdapter { + private PagerInfo[] mInfoList; + private Fragment mCurFragment; + + public BaseViewPagerAdapter(FragmentManager fm, PagerInfo[] infoList) { + super(fm); + mInfoList = infoList; + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + super.setPrimaryItem(container, position, object); + if (object instanceof Fragment) { + mCurFragment = (Fragment) object; + } + } + + public Fragment getCurFragment() { + return mCurFragment; + } + + @Override + public Fragment getItem(int position) { + PagerInfo info = mInfoList[position]; + return Fragment.instantiate(getContext(), info.clx.getName(), info.args); + } + + @Override + public int getCount() { + return mInfoList.length; + } + + @Override + public CharSequence getPageTitle(int position) { + return mInfoList[position].title; + } + + @Override + public int getItemPosition(Object object) { + return PagerAdapter.POSITION_NONE; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/Active.java b/app/src/main/java/net/oschina/app/improve/bean/Active.java new file mode 100644 index 0000000000000000000000000000000000000000..0c7d1fbaa23c0c28658e87415709cc0360d1c32e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Active.java @@ -0,0 +1,59 @@ +package net.oschina.app.improve.bean; + +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.Origin; + +import java.io.Serializable; + +/** + * Created by thanatos on 16/8/16. + */ +public class Active implements Serializable { + + private long id; + private String content; + private String pubDate; + private Author author; + private Origin origin; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public Origin getOrigin() { + return origin; + } + + public void setOrigin(Origin origin) { + this.origin = origin; + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/ApplyUser.java b/app/src/main/java/net/oschina/app/improve/bean/ApplyUser.java new file mode 100644 index 0000000000000000000000000000000000000000..086840235bbedb6cfcdac01cf49c1fd2ddd7f2e4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/ApplyUser.java @@ -0,0 +1,54 @@ +package net.oschina.app.improve.bean; + +import net.oschina.app.improve.bean.simple.Author; + +import java.io.Serializable; + +/** + * 活动出席者 + * Created by haibin + * on 2016/12/27. + */ + +public class ApplyUser extends Author { + + private EventInfo eventInfo; + + public EventInfo getEventInfo() { + return eventInfo; + } + + public void setEventInfo(EventInfo eventInfo) { + this.eventInfo = eventInfo; + } + + public static class EventInfo implements Serializable { + private String name; + private String company; + private String job; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + + public String getJob() { + return job; + } + + public void setJob(String job) { + this.job = job; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/Article.java b/app/src/main/java/net/oschina/app/improve/bean/Article.java new file mode 100644 index 0000000000000000000000000000000000000000..695dfdc1b100c296884eb5dd3788b9ca0322d247 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Article.java @@ -0,0 +1,223 @@ +package net.oschina.app.improve.bean; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/** + * 头条 + * Created by huanghaibin on 2017/10/23. + */ +@SuppressWarnings("unused") +public class Article implements Serializable { + public static final int TYPE_AD = 9999;//广告类型 + public static final int TYPE_ENGLISH = 8000;//英文类型 + public static final int TYPE_HREF = 0; + public static final int TYPE_SOFTWARE = 1; + public static final int TYPE_QUESTION = 2; + public static final int TYPE_BLOG = 3; + public static final int TYPE_TRANSLATE = 4; + public static final int TYPE_EVENT = 5; + public static final int TYPE_NEWS = 6; + public static final int TYPE_ZB = 7; + public static final int TYPE_FIND_PERSON = 11; + + private int type; + private String authorName; + private String authorId; + private String key; + private String title; + private String desc; + private String content;//详情 + private String url; + private String pubDate; + private String source; + private String softwareLogo; + private String[] imgs; + private Tag[] iTags; + private int commentCount; + private boolean favorite; + private int wordCount; + + @SerializedName("sub_type") + private int subType; + + private long readTime; + + private String titleTranslated;//中文翻译标题 + + @SerializedName("osc_id") + private long oscId; + + @SerializedName("view_count") + private int viewCount; + + public Tag[] getiTags() { + return iTags; + } + + public void setiTags(Tag[] iTags) { + this.iTags = iTags; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String[] getImgs() { + return imgs; + } + + public void setImgs(String[] imgs) { + this.imgs = imgs; + } + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public String getAuthorId() { + return authorId; + } + + public void setAuthorId(String authorId) { + this.authorId = authorId; + } + + + public long getOscId() { + return oscId; + } + + public void setOscId(long oscId) { + this.oscId = oscId; + } + + public int getViewCount() { + return viewCount; + } + + public void setViewCount(int viewCount) { + this.viewCount = viewCount; + } + + public boolean isFavorite() { + return favorite; + } + + public void setFavorite(boolean favorite) { + this.favorite = favorite; + } + + public String getSoftwareLogo() { + return softwareLogo; + } + + public void setSoftwareLogo(String softwareLogo) { + this.softwareLogo = softwareLogo; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public int getWordCount() { + return wordCount; + } + + public void setWordCount(int wordCount) { + this.wordCount = wordCount; + } + + public long getReadTime() { + return readTime; + } + + public void setReadTime(long readTime) { + this.readTime = readTime; + } + + public String getTitleTranslated() { + return titleTranslated; + } + + public void setTitleTranslated(String titleTranslated) { + this.titleTranslated = titleTranslated; + } + + public int getSubType() { + return subType; + } + + public void setSubType(int subType) { + this.subType = subType; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/Blog.java b/app/src/main/java/net/oschina/app/improve/bean/Blog.java new file mode 100644 index 0000000000000000000000000000000000000000..b5533054122ec7776a199ed9862bb6b9a753630b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Blog.java @@ -0,0 +1,100 @@ +package net.oschina.app.improve.bean; + +/** + * Created by fei on 2016/5/24. + * desc: blog bean + */ +public class Blog extends PrimaryBean { + private String title; + private String body; + private String author; + private String pubDate; + private int commentCount; + private int viewCount; + private String href; + private boolean recommend; //是否推荐 + private boolean original; //是否原创 + private int type; //博客类型 1:常规, 2: 热门 3:最近 + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + + public int getViewCount() { + return viewCount; + } + + public void setViewCount(int viewCount) { + this.viewCount = viewCount; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public boolean isRecommend() { + return recommend; + } + + public void setRecommend(boolean recommend) { + this.recommend = recommend; + } + + public boolean isOriginal() { + return original; + } + + public void setOriginal(boolean original) { + this.original = original; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + +} + diff --git a/app/src/main/java/net/oschina/app/improve/bean/BlogDetail.java b/app/src/main/java/net/oschina/app/improve/bean/BlogDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..cafda549c9caf902e706c84f410ad793fd2baff4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/BlogDetail.java @@ -0,0 +1,87 @@ +package net.oschina.app.improve.bean; + +import com.google.gson.annotations.SerializedName; + +import net.oschina.app.improve.bean.simple.About; + +import java.util.List; + +/** + * Created by qiujuer + */ +public class BlogDetail extends Blog { + private boolean favorite; + private long authorId; + private String authorPortrait; + private int authorRelation; + private String category; + @SerializedName("abstract") + private String abstractStr; + private List abouts; + private String notifyUrl; + + public String getNotifyUrl() { + return notifyUrl; + } + + public void setNotifyUrl(String notifyUrl) { + this.notifyUrl = notifyUrl; + } + + public long getAuthorId() { + return authorId; + } + + public void setAuthorId(long authorId) { + this.authorId = authorId; + } + + public String getAuthorPortrait() { + return authorPortrait; + } + + public void setAuthorPortrait(String authorPortrait) { + this.authorPortrait = authorPortrait; + } + + public int getAuthorRelation() { + return authorRelation; + } + + public void setAuthorRelation(int authorRelation) { + this.authorRelation = authorRelation; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public List getAbouts() { + return abouts; + } + + public void setAbouts(List abouts) { + this.abouts = abouts; + } + + public String getAbstract() { + return abstractStr; + } + + public void setAbstract(String abstractStr) { + this.abstractStr = abstractStr; + } + + public boolean isFavorite() { + return favorite; + } + + public void setFavorite(boolean favorite) { + this.favorite = favorite; + } +} + diff --git a/app/src/main/java/net/oschina/app/improve/bean/Collection.java b/app/src/main/java/net/oschina/app/improve/bean/Collection.java new file mode 100644 index 0000000000000000000000000000000000000000..408893ad3617bb61523f4535395467c29c3afb0a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Collection.java @@ -0,0 +1,101 @@ +package net.oschina.app.improve.bean; + +import java.io.Serializable; + +/** + * Created by haibin + * on 2016/10/18. + */ + +public class Collection implements Serializable { + private long id; + private int type; + private String title; + private String url; + private User authorUser; + private String favDate; + private int favCount; + private String body; + private int commentCount; + private boolean favorite; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public User getAuthorUser() { + return authorUser; + } + + public void setAuthorUser(User authorUser) { + this.authorUser = authorUser; + } + + public String getFavDate() { + return favDate; + } + + public void setFavDate(String favDate) { + this.favDate = favDate; + } + + public int getFavCount() { + return favCount; + } + + public void setFavCount(int favCount) { + this.favCount = favCount; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + + public boolean isFavorite() { + return favorite; + } + + public void setFavorite(boolean favorite) { + this.favorite = favorite; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/Event.java b/app/src/main/java/net/oschina/app/improve/bean/Event.java new file mode 100644 index 0000000000000000000000000000000000000000..8d3a681092787cba2269cadc0ba95bcf6969bff3 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Event.java @@ -0,0 +1,107 @@ +package net.oschina.app.improve.bean; + +/** + * Created by huanghaibin + * on 16-5-25. + */ +public class Event extends PrimaryBean { + public static final int STATUS_END = 1; + public static final int STATUS_ING = 2; + public static final int STATUS_SING_UP = 3; + + public static final int EVENT_TYPE_OSC = 1; + public static final int EVENT_TYPE_TEC = 2; + public static final int EVENT_TYPE_OTHER = 3; + public static final int EVENT_TYPE_OUTSIDE = 4; + + protected int applyCount; + protected int status; + protected int type; + protected String title; + protected String body; + protected String img; + protected String startDate; + protected String endDate; + protected String pubDate; + protected String href; + + public int getApplyCount() { + return applyCount; + } + + public void setApplyCount(int applyCount) { + this.applyCount = applyCount; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getImg() { + return img; + } + + public void setImg(String img) { + this.img = img; + } + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/EventDetail.java b/app/src/main/java/net/oschina/app/improve/bean/EventDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..248a20576d745ea4834f48af6cdbd228b6c8cfa7 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/EventDetail.java @@ -0,0 +1,153 @@ +package net.oschina.app.improve.bean; + +/** + * Created by huanghaibin + * on 16-6-13. + */ +public class EventDetail extends Event { + public static final int APPLY_STATUS_UN_SIGN = -1; + public static final int APPLY_STATUS_AUDIT = 0; + public static final int APPLY_STATUS_CONFIRMED = 1; + public static final int APPLY_STATUS_PRESENTED = 2; + public static final int APPLY_STATUS_CANCELED = 3; + public static final int APPLY_STATUS_REFUSED = 4; + + private String author; + private int authorId; + private String authorPortrait; + private int commentCount; + private int viewCount; + private String spot; + private String location; + private String city; + private String costDesc; + private boolean favorite; + private EventRemark remark; + private int applyStatus; + private String invitationImg; + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public int getAuthorId() { + return authorId; + } + + public void setAuthorId(int authorId) { + this.authorId = authorId; + } + + public String getAuthorPortrait() { + return authorPortrait; + } + + public void setAuthorPortrait(String authorPortrait) { + this.authorPortrait = authorPortrait; + } + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + + public int getViewCount() { + return viewCount; + } + + public void setViewCount(int viewCount) { + this.viewCount = viewCount; + } + + public String getSpot() { + return spot; + } + + public void setSpot(String spot) { + this.spot = spot; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getCostDesc() { + return costDesc; + } + + public void setCostDesc(String costDesc) { + this.costDesc = costDesc; + } + + public boolean isFavorite() { + return favorite; + } + + public void setFavorite(boolean favorite) { + this.favorite = favorite; + } + + public EventRemark getRemark() { + return remark; + } + + public void setRemark(EventRemark remark) { + this.remark = remark; + } + + public int getApplyStatus() { + return applyStatus; + } + + public void setApplyStatus(int applyStatus) { + this.applyStatus = applyStatus; + } + + public String getInvitationImg() { + return invitationImg; + } + + public void setInvitationImg(String invitationImg) { + this.invitationImg = invitationImg; + } + + public static class EventRemark { + private String tip; + private String select; + + public String getTip() { + return tip; + } + + public void setTip(String tip) { + this.tip = tip; + } + + public String getSelect() { + return select; + } + + public void setSelect(String select) { + this.select = select; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/EventSignIn.java b/app/src/main/java/net/oschina/app/improve/bean/EventSignIn.java new file mode 100644 index 0000000000000000000000000000000000000000..9f29d8dd7731380897a713bd850d37c960b69231 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/EventSignIn.java @@ -0,0 +1,68 @@ +package net.oschina.app.improve.bean; + +import java.io.Serializable; + +/** + * Created by fei + * on 2016/12/1. + * desc: 活动签到bean + */ + +public class EventSignIn implements Serializable { + + private int optStatus; //1: 成功 2: 活动进行中未报名 3: 活动已结束/活动报名已截止 4: 已签到 + private String message; //1.您是开源软件作者,本次活动免费~ 2.需缴纳50元!(金额由活动决定)3.未报名,不可签到 4.活动已结束/活动报名已经截止 5已签到 + private int cost; //缴纳金额(单位分) + private String href; //分享推广的链接 + private String costMessage; + + public int getOptStatus() { + return optStatus; + } + + public void setOptStatus(int optStatus) { + this.optStatus = optStatus; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getCost() { + return cost; + } + + public void setCost(int cost) { + this.cost = cost; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getCostMessage() { + return costMessage; + } + + public void setCostMessage(String costMessage) { + this.costMessage = costMessage; + } + + @Override + public String toString() { + return "EventSignIn{" + + "optStatus=" + optStatus + + ", message='" + message + '\'' + + ", cost=" + cost + + ", href='" + href + '\'' + + '}'; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/Launcher.java b/app/src/main/java/net/oschina/app/improve/bean/Launcher.java new file mode 100644 index 0000000000000000000000000000000000000000..262fa9f01af147121c2d583354ac90dae3c4b207 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Launcher.java @@ -0,0 +1,47 @@ +package net.oschina.app.improve.bean; + +import java.io.Serializable; + +/** + * 启动页数据 + * Created by huanghaibin on 2017/11/25. + */ + +public class Launcher implements Serializable { + private boolean isAd;//是否是广告 + private String imgUrl;//图片连接 + private String href;//打开链接 + private boolean isExpired;//是否过期 + + public boolean isAd() { + return isAd; + } + + public void setAd(boolean ad) { + isAd = ad; + } + + public String getImgUrl() { + return imgUrl; + } + + public void setImgUrl(String imgUrl) { + this.imgUrl = imgUrl; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public boolean isExpired() { + return isExpired; + } + + public void setExpired(boolean expired) { + isExpired = expired; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/Mention.java b/app/src/main/java/net/oschina/app/improve/bean/Mention.java new file mode 100644 index 0000000000000000000000000000000000000000..2ee972d9ff676b88b28d1e276b2692d2d3990588 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Mention.java @@ -0,0 +1,77 @@ +package net.oschina.app.improve.bean; + +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.Origin; + +import java.io.Serializable; + +/** + * AT我的 + * Created by huanghaibin_dev + * on 2016/8/16. + */ +public class Mention implements Serializable { + private long id; + private String content; + private String pubDate; + private Author author; + private Origin origin; + private int appClient; + private int commentCount; + + public int getAppClient() { + return appClient; + } + + public void setAppClient(int appClient) { + this.appClient = appClient; + } + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public Origin getOrigin() { + return origin; + } + + public void setOrigin(Origin origin) { + this.origin = origin; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/Message.java b/app/src/main/java/net/oschina/app/improve/bean/Message.java new file mode 100644 index 0000000000000000000000000000000000000000..b1858042c37eb05bcc44c1c8f933d874082eab3a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Message.java @@ -0,0 +1,77 @@ +package net.oschina.app.improve.bean; + +import java.io.Serializable; + +/** + * 私信 + * Created by huanghaibin_dev + * on 2016/8/16. + */ +public class Message implements Serializable { + public static final int TYPE_TEXT = 1; + public static final int TYPE_IMAGE = 3; + public static final int TYPE_FILE = 5; + private long id; + private String content; + private String pubDate; + private int type; + private String resource; + private User sender; + private User receiver; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getResource() { + return resource; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public User getSender() { + return sender; + } + + public void setSender(User sender) { + this.sender = sender; + } + + public User getReceiver() { + return receiver; + } + + public void setReceiver(User receiver) { + this.receiver = receiver; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/NearbyResult.java b/app/src/main/java/net/oschina/app/improve/bean/NearbyResult.java new file mode 100644 index 0000000000000000000000000000000000000000..478677437d0c8c3a9547ddbbd3ec9f011a9e887d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/NearbyResult.java @@ -0,0 +1,76 @@ +package net.oschina.app.improve.bean; + +import android.support.annotation.NonNull; + +import java.io.Serializable; + +/** + * Created by thanatosx + * on 2016/12/23. + * Updated by fei + * on 2017/01/13. + */ + +public class NearbyResult implements Serializable, Comparable { + + private User user; + private Nearby nearby; + + public NearbyResult(User user, Nearby nearby) { + this.user = user; + this.nearby = nearby; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Nearby getNearby() { + return nearby; + } + + public void setNearby(Nearby nearby) { + this.nearby = nearby; + } + + @Override + public int compareTo(@NonNull NearbyResult nearbyResult) { + return nearby.distance - nearbyResult.getNearby().distance; + } + + public static class Nearby implements Serializable { + private int distance; + private String mobileName; + private String mobileOS; + + public int getDistance() { + return distance; + } + + public void setDistance(int distance) { + this.distance = distance; + } + + public String getMobileName() { + return mobileName; + } + + public void setMobileName(String mobileName) { + this.mobileName = mobileName; + } + + public String getMobileOS() { + return mobileOS; + } + + public void setMobileOS(String mobileOS) { + this.mobileOS = mobileOS; + } + + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/News.java b/app/src/main/java/net/oschina/app/improve/bean/News.java new file mode 100644 index 0000000000000000000000000000000000000000..d3346a512828dbc36712c04ca49665cecfff6482 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/News.java @@ -0,0 +1,99 @@ +package net.oschina.app.improve.bean; + +/** + * Created by huanghaibin + * on 16-5-25. + */ +public class News extends PrimaryBean { + + public static final int TYPE_HREF = 0; + public static final int TYPE_SOFTWARE = 1; + public static final int TYPE_QUESTION = 2; + public static final int TYPE_BLOG = 3; + public static final int TYPE_TRANSLATE = 4; + public static final int TYPE_EVENT = 5; + public static final int TYPE_NEWS = 6; + public static final int TYPE_FIND_PERSON = 11; + + protected int commentCount; + protected int type; + protected boolean recommend; + protected String title; + protected String body; + protected String author; + protected String href; + protected String pubDate; + protected int viewCount; + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public boolean isRecommend() { + return recommend; + } + + public void setRecommend(boolean recommend) { + this.recommend = recommend; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public int getViewCount() { + return viewCount; + } + + public void setViewCount(int viewCount) { + this.viewCount = viewCount; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/NewsDetail.java b/app/src/main/java/net/oschina/app/improve/bean/NewsDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..55774e6042736d0304135cad768bdbbd64728e92 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/NewsDetail.java @@ -0,0 +1,94 @@ +package net.oschina.app.improve.bean; + +import com.google.gson.annotations.Expose; + +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.bean.simple.UserRelation; + +import java.util.List; + +/** + * Created by fei on 2016/6/13. + * desc: + */ +public class NewsDetail extends News { + + private boolean favorite; + private long authorId; + private String authorPortrait; + private List abouts; + private Software software; + @Expose + private UserRelation userRelation; + @Expose + private List comments; + + public boolean isFavorite() { + return favorite; + } + + public void setFavorite(boolean favorite) { + this.favorite = favorite; + } + + @Override + public String getHref() { + return href; + } + + @Override + public void setHref(String href) { + this.href = href; + } + + public long getAuthorId() { + return authorId; + } + + public void setAuthorId(long authorId) { + this.authorId = authorId; + } + + public String getAuthorPortrait() { + return authorPortrait; + } + + public void setAuthorPortrait(String authorPortrait) { + this.authorPortrait = authorPortrait; + } + + public List getAbouts() { + return abouts; + } + + public void setAbouts(List abouts) { + this.abouts = abouts; + } + + public Software getSoftware() { + return software; + } + + public void setSoftware(Software software) { + this.software = software; + } + + + public UserRelation getUserRelation() { + return userRelation; + } + + public void setUserRelation(UserRelation userRelation) { + this.userRelation = userRelation; + } + + public List getComments() { + return comments; + } + + public void setComments(List comments) { + this.comments = comments; + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/PrimaryBean.java b/app/src/main/java/net/oschina/app/improve/bean/PrimaryBean.java new file mode 100644 index 0000000000000000000000000000000000000000..d60110315b9f89ba9d797164bc4488b867fc6c5e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/PrimaryBean.java @@ -0,0 +1,21 @@ +package net.oschina.app.improve.bean; + +import java.io.Serializable; + +/** + * Created by qiujuer + * on 2016/11/22. + *

    + * 主要值 + */ +public class PrimaryBean implements Serializable { + private long id; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/Question.java b/app/src/main/java/net/oschina/app/improve/bean/Question.java new file mode 100644 index 0000000000000000000000000000000000000000..efaaea67faceb9f72d56c88a8e87b46f05750841 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Question.java @@ -0,0 +1,80 @@ +package net.oschina.app.improve.bean; + +/** + * Created by fei on 2016/5/24. + * desc: question bean + */ +public class Question extends PrimaryBean { + private String title; + private String body; + private String author; + private long authorId; + private String authorPortrait; //用户头像 + private String pubDate; //发布时间 + private int commentCount; //评论次数 + private int viewCount; //浏览次数 + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public long getAuthorId() { + return authorId; + } + + public void setAuthorId(long authorId) { + this.authorId = authorId; + } + + public String getAuthorPortrait() { + return authorPortrait; + } + + public void setAuthorPortrait(String authorPortrait) { + this.authorPortrait = authorPortrait; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + + public int getViewCount() { + return viewCount; + } + + public void setViewCount(int viewCount) { + this.viewCount = viewCount; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/QuestionDetail.java b/app/src/main/java/net/oschina/app/improve/bean/QuestionDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..6bb9a8be64bc78cf57bbd5ca44102ccbaf5bb84f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/QuestionDetail.java @@ -0,0 +1,39 @@ +package net.oschina.app.improve.bean; + +import java.util.List; + +/** + * 问答详情bean + */ +public class QuestionDetail extends Question { + + private boolean favorite; + private String href; + private List tags; + + public boolean isFavorite() { + return favorite; + } + + public void setFavorite(boolean favorite) { + this.favorite = favorite; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } +} + diff --git a/app/src/main/java/net/oschina/app/improve/bean/Report.java b/app/src/main/java/net/oschina/app/improve/bean/Report.java new file mode 100644 index 0000000000000000000000000000000000000000..797e150b0dfac1057473b734533df048f57436de --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Report.java @@ -0,0 +1,56 @@ +package net.oschina.app.improve.bean; + +import java.io.Serializable; + +/** + * Created by haibin + * on 2016/12/26. + */ + +public class Report implements Serializable { + private long id; + private int type; + private String href; + private int reason; + private String memo; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public int getReason() { + return reason; + } + + public void setReason(int reason) { + this.reason = reason; + } + + public String getMemo() { + return memo; + } + + public void setMemo(String memo) { + this.memo = memo; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/SignUpEventOptions.java b/app/src/main/java/net/oschina/app/improve/bean/SignUpEventOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..6058b9805692a7117cb08c02adc5abcebcd7b553 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/SignUpEventOptions.java @@ -0,0 +1,113 @@ +package net.oschina.app.improve.bean; + +import java.io.Serializable; +import java.util.List; + +/** + * Created by haibin + * on 2016/12/5. + */ + +public class SignUpEventOptions implements Serializable { + public static final int FORM_TYPE_TEXT = 0; + public static final int FORM_TYPE_TEXT_AREA = 1; + public static final int FORM_TYPE_SELECT = 2; + public static final int FORM_TYPE_CHECK_BOX = 3; + public static final int FORM_TYPE_RADIO = 4; + public static final int FORM_TYPE_EMAIL = 5; + public static final int FORM_TYPE_DATE = 6; + public static final int FORM_TYPE_MOBILE = 7; + public static final int FORM_TYPE_NUMBER = 8; + public static final int FORM_TYPE_URL = 9; + + private String key; + private String value;//用户输入的参数 + private List selectList;//用户多选的参数 + private int keyType; + private int formType; + private String label; + private String option; + private String optionStatus; + private String defaultValue; + private boolean required; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public List getSelectList() { + return selectList; + } + + public void setSelectList(List selectList) { + this.selectList = selectList; + } + + public void setValue(String value) { + this.value = value; + } + + public int getKeyType() { + return keyType; + } + + public void setKeyType(int keyType) { + this.keyType = keyType; + } + + public int getFormType() { + return formType; + } + + public void setFormType(int formType) { + this.formType = formType; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getOption() { + return option; + } + + public void setOption(String option) { + this.option = option; + } + + public String getOptionStatus() { + return optionStatus; + } + + public void setOptionStatus(String optionStatus) { + this.optionStatus = optionStatus; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public boolean isRequired() { + return required; + } + + public void setRequired(boolean required) { + this.required = required; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/Software.java b/app/src/main/java/net/oschina/app/improve/bean/Software.java new file mode 100644 index 0000000000000000000000000000000000000000..d44983e787b281357bd90c7b1554f0d99d03abdb --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Software.java @@ -0,0 +1,27 @@ +package net.oschina.app.improve.bean; + +/** + * Created by fei on 2016/6/20. + * desc: + */ +public class Software extends PrimaryBean { + private String name; + private String href; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/SoftwareDetail.java b/app/src/main/java/net/oschina/app/improve/bean/SoftwareDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..644d5a34c29bfaa13e9352a2ab1a1dd13008bddb --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/SoftwareDetail.java @@ -0,0 +1,220 @@ +package net.oschina.app.improve.bean; + +import net.oschina.app.improve.bean.simple.About; + +import java.util.List; + +/** + * Created by fei on 2016/6/20. + * desc: + */ +public class SoftwareDetail extends Software { + + private String extName; //软件别名 + private String logo;//logo,有null的情况哦 + private String body; //软件资讯内容 + private String author;//发布者 + private long authorId;//发布者id + private String authorPortrait;//用户头像url + private String license;//软件license信息 + private String homePage;//首页 + private String document;//文档 + private String download;//下载地址 + private String language;//开发语言 + private String supportOS;//支持平台 + private String collectionDate; //收录时间 + private String pubDate;//发布时间 + private int commentCount; //评论量,实际是动弹量 + private int viewCount;//浏览量 + private boolean favorite;//是否收藏 + private boolean recommend;//是否推荐 + private String identification; //唯一标示 + private List abouts; //相关推荐 + + public String getExtName() { + return extName; + } + + public void setExtName(String extName) { + this.extName = extName; + } + + public String getLogo() { + return logo; + } + + public void setLogo(String logo) { + this.logo = logo; + } + + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public long getAuthorId() { + return authorId; + } + + public void setAuthorId(long authorId) { + this.authorId = authorId; + } + + public String getAuthorPortrait() { + return authorPortrait; + } + + public void setAuthorPortrait(String authorPortrait) { + this.authorPortrait = authorPortrait; + } + + public String getLicense() { + return license; + } + + public void setLicense(String license) { + this.license = license; + } + + public String getHomePage() { + return homePage; + } + + public void setHomePage(String homePage) { + this.homePage = homePage; + } + + public String getDocument() { + return document; + } + + public void setDocument(String document) { + this.document = document; + } + + public String getDownload() { + return download; + } + + public void setDownload(String download) { + this.download = download; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getSupportOS() { + return supportOS; + } + + public void setSupportOS(String supportOS) { + this.supportOS = supportOS; + } + + public String getCollectionDate() { + return collectionDate; + } + + public void setCollectionDate(String collectionDate) { + this.collectionDate = collectionDate; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + + public int getViewCount() { + return viewCount; + } + + public void setViewCount(int viewCount) { + this.viewCount = viewCount; + } + + public boolean isFavorite() { + return favorite; + } + + public void setFavorite(boolean favorite) { + this.favorite = favorite; + } + + public boolean isRecommend() { + return recommend; + } + + public void setRecommend(boolean recommend) { + this.recommend = recommend; + } + + public List getAbouts() { + return abouts; + } + + public void setAbouts(List abouts) { + this.abouts = abouts; + } + + public String getIdentification() { + return identification; + } + + public void setIdentification(String identification) { + this.identification = identification; + } + + @Override + public String toString() { + return "SoftwareDetail{" + + "extName='" + extName + '\'' + + ", logo='" + logo + '\'' + + ", body='" + body + '\'' + + ", author='" + author + '\'' + + ", authorId=" + authorId + + ", authorPortrait='" + authorPortrait + '\'' + + ", license='" + license + '\'' + + ", homePage='" + homePage + '\'' + + ", document='" + document + '\'' + + ", download='" + download + '\'' + + ", language='" + language + '\'' + + ", supportOS='" + supportOS + '\'' + + ", collectionDate='" + collectionDate + '\'' + + ", pubDate='" + pubDate + '\'' + + ", commentCount=" + commentCount + + ", viewCount=" + viewCount + + ", favorite=" + favorite + + ", recommend=" + recommend + + ", identification='" + identification + '\'' + + ", abouts=" + abouts + + '}'; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/SubBean.java b/app/src/main/java/net/oschina/app/improve/bean/SubBean.java new file mode 100644 index 0000000000000000000000000000000000000000..dedb6b7af0e249f78af77a74d864373031f5e2b2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/SubBean.java @@ -0,0 +1,348 @@ +package net.oschina.app.improve.bean; + +import android.support.annotation.NonNull; +import android.text.TextUtils; + +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.bean.simple.Author; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Created by haibin + * on 2016/10/26. + */ + +public class SubBean implements Serializable ,Comparable{ + private static final long serialVersionUID = -5343222344464021662L; + private String cacheKey; + + private long id; + private String title; + private String body; + private String pubDate; + private String href; + private int type; + private boolean favorite; + private String summary; + private Author author; + private List images; + private HashMap extra; + private String[] tags; + private Tag[] iTags; + private Statistics statistics; + private ArrayList abouts; + private Software software; + private long newsId; + + public boolean isOriginal() { + if (tags != null) { + for (String tag : tags) { + if ("original".equalsIgnoreCase(tag)) { + return true; + } + } + } + return false; + } + + public Tag[] getiTags() { + return iTags; + } + + public void setiTags(Tag[] iTags) { + this.iTags = iTags; + } + + public boolean isAD() { + if (tags != null) { + for (String tag : tags) { + if ("ad".equalsIgnoreCase(tag)) { + return true; + } + } + } + return false; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public boolean isStick() { + if (tags != null) { + for (String tag : tags) { + if ("stick".equalsIgnoreCase(tag)) { + return true; + } + } + } + return false; + } + + public boolean isRecommend() { + if (tags != null) { + for (String tag : tags) { + if ("recommend".equalsIgnoreCase(tag)) { + return true; + } + } + } + return false; + } + + public long getNewsId() { + return newsId; + } + + public void setNewsId(long newsId) { + this.newsId = newsId; + } + + public List getImages() { + return images; + } + + public void setImages(List images) { + this.images = images; + } + + public boolean isFavorite() { + return favorite; + } + + public void setFavorite(boolean favorite) { + this.favorite = favorite; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public HashMap getExtra() { + return extra; + } + + public void setExtra(HashMap extra) { + this.extra = extra; + } + + public ArrayList getAbouts() { + return abouts; + } + + public void setAbouts(ArrayList abouts) { + this.abouts = abouts; + } + + public String[] getTags() { + return tags; + } + + public void setTags(String[] tags) { + this.tags = tags; + } + + public Statistics getStatistics() { + return statistics; + } + + public void setStatistics(Statistics statistics) { + this.statistics = statistics; + } + + public Software getSoftware() { + return software; + } + + public void setSoftware(Software software) { + this.software = software; + } + + public String getKey() { + if (cacheKey == null) + cacheKey = String.format("t:%s,id:%s", getType(), getId() == 0 ? getHref().hashCode() : getId()); + return cacheKey; + } + + @Override + public int compareTo(@NonNull SubBean o) { + if(TextUtils.isEmpty(o.getPubDate())) + return 1; + if(TextUtils.isEmpty(pubDate)){ + return -1; + } + int c = pubDate.compareTo(o.getPubDate());//倒序排序 + if(c >= 1){ + return -1; + } + if(c < 0){ + return 1; + } + return 0; + } + + public static class Image implements Serializable { + private String href; + private String thumb; + private int w; + private int h; + private String type; + private String name; + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public String getThumb() { + return thumb; + } + + public void setThumb(String thumb) { + this.thumb = thumb; + } + + public int getW() { + return w; + } + + public void setW(int w) { + this.w = w; + } + + public int getH() { + return h; + } + + public void setH(int h) { + this.h = h; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class Statistics implements Serializable { + private int comment; + private int view; + private int like; + private int transmit; + private int favCount; + + public int getComment() { + return comment; + } + + public void setComment(int comment) { + this.comment = comment; + } + + public int getView() { + return view; + } + + public void setView(int view) { + this.view = view; + } + + public int getLike() { + return like; + } + + public void setLike(int like) { + this.like = like; + } + + public int getTransmit() { + return transmit; + } + + public void setTransmit(int transmit) { + this.transmit = transmit; + } + + public int getFavCount() { + return favCount; + } + + public void setFavCount(int favCount) { + this.favCount = favCount; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/SubTab.java b/app/src/main/java/net/oschina/app/improve/bean/SubTab.java new file mode 100644 index 0000000000000000000000000000000000000000..200059d99803414addf12b6448b6ac2ab7b680e0 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/SubTab.java @@ -0,0 +1,188 @@ +package net.oschina.app.improve.bean; + +import android.text.TextUtils; + +import net.oschina.app.api.ApiHttpClient; +import net.oschina.app.util.TLog; + +import java.io.Serializable; + +/** + * 分栏Model + * Created by thanatosx + * on 16/10/26. + */ +public class SubTab implements Serializable { + + public static final String TAG_NEW = "new"; + public static final String TAG_HOT = "hot"; + + public static final int BANNER_CATEGORY_NEWS = 1; + public static final int BANNER_CATEGORY_EVENT = 3; + public static final int BANNER_CATEGORY_BLOG = 4; + + private String token; + private String name; + private boolean fixed; + private boolean needLogin; + private String tag; + private int type; + private int subtype; + private int order; + private String href; + private Banner banner; + private boolean isActived; + + + public class Banner implements Serializable { + private int catalog; + private String href; + + public int getCatalog() { + return catalog; + } + + public void setCatalog(int catalog) { + this.catalog = catalog; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + @Override + public String toString() { + return "Banner{" + + "catalog=" + catalog + + ", href='" + href + '\'' + + '}'; + } + } + + @Override + public int hashCode() { + return this.token == null ? 0 : this.token.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj != null && obj instanceof SubTab) { + SubTab tab = (SubTab) obj; + if (tab.getToken() == null) return false; + if (this.token == null) return false; + return tab.getToken().equals(this.token); + } + return false; + } + + public boolean isActived() { + return isActived; + } + + public void setActived(boolean actived) { + isActived = actived; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isFixed() { + return fixed; + } + + public void setFixed(boolean fixed) { + this.fixed = fixed; + } + + public boolean isNeedLogin() { + return needLogin; + } + + public void setNeedLogin(boolean needLogin) { + this.needLogin = needLogin; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getSubtype() { + return subtype; + } + + public void setSubtype(int subtype) { + this.subtype = subtype; + } + + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } + + public String getHref() { + String url = String.format("action/apiv2/sub_list?token=%s", TextUtils.isEmpty(token) ? "" : token); + url = String.format(ApiHttpClient.API_URL, url); + TLog.log("SubTab getHref:" + url); + return url; + } + + public void setHref(String href) { + this.href = href; + } + + public Banner getBanner() { + return banner; + } + + public void setBanner(Banner banner) { + this.banner = banner; + } + + @Override + public String toString() { + return "SubTab{" + + "token='" + token + '\'' + + ", name='" + name + '\'' + + ", fixed=" + fixed + + ", needLogin=" + needLogin + + ", tag='" + tag + '\'' + + ", type=" + type + + ", subtype=" + subtype + + ", order=" + order + + ", href='" + href + '\'' + + ", banner=" + banner + + '}'; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/Tag.java b/app/src/main/java/net/oschina/app/improve/bean/Tag.java new file mode 100644 index 0000000000000000000000000000000000000000..385fd3a1c121e3ffb7037d78e5b16dddb334b043 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Tag.java @@ -0,0 +1,38 @@ +package net.oschina.app.improve.bean; + +import java.io.Serializable; + +/** + * 标签化 + * Created by huanghaibin on 2017/11/23. + */ + +public class Tag implements Serializable { + private long oscId; + private String name; + private String tagId; + + public long getOscId() { + return oscId; + } + + public void setOscId(long oscId) { + this.oscId = oscId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTagId() { + return tagId; + } + + public void setTagId(String tagId) { + this.tagId = tagId; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/Tags.java b/app/src/main/java/net/oschina/app/improve/bean/Tags.java new file mode 100644 index 0000000000000000000000000000000000000000..d833e7cbd6e72d543c33afc2cda3dab86de8ee9d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Tags.java @@ -0,0 +1,47 @@ +package net.oschina.app.improve.bean; + +import java.io.Serializable; + +/** + * 用户标签 + * Created by haibin on 2018/05/28. + */ +public class Tags implements Serializable{ + private int id; + private String name; + private boolean related; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isRelated() { + return related; + } + + public void setRelated(boolean related) { + this.related = related; + } + + @Override + public boolean equals(Object obj) { + if(obj == null) + return false; + if(obj instanceof Tags){ + return ((Tags) obj).id == id; + } + return super.equals(obj); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/TranslationDetail.java b/app/src/main/java/net/oschina/app/improve/bean/TranslationDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..a70dbabaa5e13dec773f7b2b87de1191d7fb454c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/TranslationDetail.java @@ -0,0 +1,116 @@ +package net.oschina.app.improve.bean; + +/** + * Created by fei on 2016/6/28. + * desc: question bean + */ +public class TranslationDetail extends PrimaryBean { + private String title; + private String originlTitle; //原始文章名称,比如英文的 + private String body; + private String author; + private long authorId; + private String authorPortrait; //用户头像 + private int authorRelation; //与翻译作者的关系 + private String pubDate; //发布时间 + private int commentCount; //评论次数 + private int viewCount; //浏览次数 + private String href; + private boolean favorite; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getOriginlTitle() { + return originlTitle; + } + + public void setOriginlTitle(String originlTitle) { + this.originlTitle = originlTitle; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public long getAuthorId() { + return authorId; + } + + public void setAuthorId(long authorId) { + this.authorId = authorId; + } + + public String getAuthorPortrait() { + return authorPortrait; + } + + public void setAuthorPortrait(String authorPortrait) { + this.authorPortrait = authorPortrait; + } + + public int getAuthorRelation() { + return authorRelation; + } + + public void setAuthorRelation(int authorRelation) { + this.authorRelation = authorRelation; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + + public int getViewCount() { + return viewCount; + } + + public void setViewCount(int viewCount) { + this.viewCount = viewCount; + } + + public boolean isFavorite() { + return favorite; + } + + public void setFavorite(boolean favorite) { + this.favorite = favorite; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/Tweet.java b/app/src/main/java/net/oschina/app/improve/bean/Tweet.java new file mode 100644 index 0000000000000000000000000000000000000000..6e6d8840a6bf9517dafb0e5397f592d19d85ffbc --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Tweet.java @@ -0,0 +1,325 @@ +package net.oschina.app.improve.bean; + +import android.text.TextUtils; + +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.common.utils.CollectionUtil; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Created by huanghaibin_dev + * on 2016/7/18. + */ +public class Tweet implements Serializable { + private long id; + private String content; + private int appClient; + private int commentCount; + private int likeCount; + private boolean liked; + private String pubDate; + private Author author; + private Code code; + private String href; + private Audio[] audio; + private Image[] images; + private Statistics statistics; + private About about; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public int getAppClient() { + return appClient; + } + + public void setAppClient(int appClient) { + this.appClient = appClient; + } + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public int getLikeCount() { + return likeCount; + } + + public void setLikeCount(int likeCount) { + this.likeCount = likeCount; + } + + public boolean isLiked() { + return liked; + } + + public void setLiked(boolean liked) { + this.liked = liked; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public Code getCode() { + return code; + } + + public void setCode(Code code) { + this.code = code; + } + + public Audio[] getAudio() { + return audio; + } + + public void setAudio(Audio[] audio) { + this.audio = audio; + } + + public Image[] getImages() { + return images; + } + + public void setImages(Image[] images) { + this.images = images; + } + + public About getAbout() { + return about; + } + + public void setAbout(About about) { + this.about = about; + } + + public Statistics getStatistics() { + return statistics; + } + + public void setStatistics(Statistics statistics) { + this.statistics = statistics; + } + + public static class Code implements Serializable { + private String brush; + private String content; + + public String getBrush() { + return brush; + } + + public void setBrush(String brush) { + this.brush = brush; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } + + public static class Audio implements Serializable { + private String href; + private long timeSpan; + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public long getTimeSpan() { + return timeSpan; + } + + public void setTimeSpan(long timeSpan) { + this.timeSpan = timeSpan; + } + } + + public static class Image implements Serializable { + private String thumb; + private String href; + private String name; + private int type; + private int w; + private int h; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getThumb() { + return thumb; + } + + public void setThumb(String thumb) { + this.thumb = thumb; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public int getW() { + return w; + } + + public void setW(int w) { + this.w = w; + } + + public int getH() { + return h; + } + + public void setH(int h) { + this.h = h; + } + + public static Image create(String href) { + Image image = new Image(); + image.thumb = href; + image.href = href; + return image; + } + + public static String[] getImagePath(Image[] images) { + if (images == null || images.length == 0) + return null; + + List paths = new ArrayList<>(); + for (Image image : images) { + if (check(image)) + paths.add(image.href); + } + + return CollectionUtil.toArray(paths, String.class); + } + + public static boolean check(Image image) { + return image != null + && !TextUtils.isEmpty(image.getThumb()) + && !TextUtils.isEmpty(image.getHref()); + } + } + + public static class Statistics implements Serializable { + private int comment; + private int transmit; + private int like; + + public int getLike() { + return like; + } + + public void setLike(int like) { + this.like = like; + } + + public int getComment() { + return comment; + } + + public void setComment(int comment) { + this.comment = comment; + } + + public int getTransmit() { + return transmit; + } + + public void setTransmit(int transmit) { + this.transmit = transmit; + } + } + + @Override + public String toString() { + return "Tweet{" + + "id=" + id + + ", content='" + content + '\'' + + ", appClient=" + appClient + + ", commentCount=" + commentCount + + ", likeCount=" + likeCount + + ", liked=" + liked + + ", pubDate='" + pubDate + '\'' + + ", author=" + author + + ", code=" + code + + ", audio=" + Arrays.toString(audio) + + ", images=" + Arrays.toString(images) + + '}'; + } + + @Override + public boolean equals(Object o) { + if (o != null && o instanceof Tweet) { + return ((Tweet) o).getId() == id; + } + return false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/User.java b/app/src/main/java/net/oschina/app/improve/bean/User.java new file mode 100644 index 0000000000000000000000000000000000000000..424cd3a7acea2cd4c1b9bedd58850decc50e420a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/User.java @@ -0,0 +1,291 @@ +package net.oschina.app.improve.bean; + +import net.oschina.app.improve.bean.simple.Author; + +import java.io.Serializable; + +/** + * 用户信息类 + */ +public class User extends Author { + public static final int RELATION_TYPE_BOTH = 0x01;// 双方互为粉丝 + public static final int RELATION_TYPE_ONLY_FANS_HIM = 0x02;// 你单方面关注他 + public static final int RELATION_TYPE_ONLY_FANS_ME = 0x03;// 只有他关注我 + public static final int RELATION_TYPE_NULL = 0x04;// 互不关注 + + public static final int GENDER_MALE = 1; // 男 + public static final int GENDER_FEMALE = 2; // 女 + + // More + private String desc; + + //个性后缀 + private String suffix; + private More more; + private Statistics statistics; + // 本地缓存多余信息 + private String cookie; + + public User() { + more = new More(); + statistics = new Statistics(); + } + + public String getSuffix() { + return suffix; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public More getMore() { + return more; + } + + public void setMore(More more) { + this.more = more; + } + + public Statistics getStatistics() { + return statistics; + } + + public void setStatistics(Statistics statistics) { + this.statistics = statistics; + } + + public String getCookie() { + return cookie; + } + + public void setCookie(String cookie) { + this.cookie = cookie; + } + + @Override + public String toString() { + return "User{" + "id=" + id + + ", name='" + name + '\'' + + ", portrait='" + portrait + '\'' + + "gender=" + gender + + ", desc='" + desc + '\'' + + ", relation=" + relation + + ", suffix='" + suffix + '\'' + + ", more=" + more + + ", statistics=" + statistics + + '}'; + } + + + public static class More implements Serializable { + private String joinDate; + private String city; + private String province; + private String expertise; + private String platform; + private String company; + private String position; + private int[] field; + private int skill[]; + public String getJoinDate() { + return joinDate; + } + + public void setJoinDate(String joinDate) { + this.joinDate = joinDate; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getExpertise() { + return expertise; + } + + public void setExpertise(String expertise) { + this.expertise = expertise; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + + public String getProvince() { + return province; + } + + public void setProvince(String province) { + this.province = province; + } + + public int[] getField() { + return field; + } + + public void setField(int[] field) { + this.field = field; + } + + public int[] getSkill() { + return skill; + } + + public void setSkill(int[] skill) { + this.skill = skill; + } + + @Override + public String toString() { + return "More{" + + "joinDate='" + joinDate + '\'' + + ", city='" + city + '\'' + + ", expertise='" + expertise + '\'' + + ", platform='" + platform + '\'' + + ", company='" + company + '\'' + + ", position='" + position + '\'' + + '}'; + } + } + + public static class Statistics implements Serializable { + private int honorScore; + private int activeScore; + private int score; + private int tweet; + private int collect; + private int fans; + private int follow; + private int blog; + private int answer; + private int discuss; + + + public int getScore() { + return score; + } + + public void setScore(int score) { + this.score = score; + } + + public int getTweet() { + return tweet; + } + + public void setTweet(int tweet) { + this.tweet = tweet; + } + + public int getCollect() { + return collect; + } + + public void setCollect(int collect) { + this.collect = collect; + } + + public int getFans() { + return fans; + } + + public void setFans(int fans) { + this.fans = fans; + } + + public int getFollow() { + return follow; + } + + public void setFollow(int follow) { + this.follow = follow; + } + + public int getBlog() { + return blog; + } + + public void setBlog(int blog) { + this.blog = blog; + } + + public int getAnswer() { + return answer; + } + + public void setAnswer(int answer) { + this.answer = answer; + } + + public int getDiscuss() { + return discuss; + } + + public void setDiscuss(int discuss) { + this.discuss = discuss; + } + + public int getHonorScore() { + return honorScore; + } + + public void setHonorScore(int honorScore) { + this.honorScore = honorScore; + } + + public int getActiveScore() { + return activeScore; + } + + public void setActiveScore(int activeScore) { + this.activeScore = activeScore; + } + + @Override + public String toString() { + return "Statistics{" + + "score=" + score + + ", tweet=" + tweet + + ", collect=" + collect + + ", fans=" + fans + + ", follow=" + follow + + ", blog=" + blog + + ", answer=" + answer + + ", discuss=" + discuss + + '}'; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/Version.java b/app/src/main/java/net/oschina/app/improve/bean/Version.java new file mode 100644 index 0000000000000000000000000000000000000000..26b9a05a6317f2dad0ac23fb87442b97b9f8397e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/Version.java @@ -0,0 +1,65 @@ +package net.oschina.app.improve.bean; + +import java.io.Serializable; + +/** + * Created by haibin + * on 2016/10/19. + */ + +public class Version implements Serializable { + private String code; + private String name; + private String release; + private String message; + private String downloadUrl; + private String publishDate; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getRelease() { + return release; + } + + public void setRelease(String release) { + this.release = release; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getPublishDate() { + return publishDate; + } + + public void setPublishDate(String publishDate) { + this.publishDate = publishDate; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/base/PageBean.java b/app/src/main/java/net/oschina/app/improve/bean/base/PageBean.java new file mode 100644 index 0000000000000000000000000000000000000000..3684109ca61e59f92801e94c2942a661db4a9376 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/base/PageBean.java @@ -0,0 +1,86 @@ +package net.oschina.app.improve.bean.base; + +import java.io.Serializable; +import java.util.List; + +/** + * Created by huanghaibin + * on 16-5-24. + */ +public class PageBean implements Serializable { + private List items; + private String nextPageToken; + private String prevPageToken; + private int requestCount; + private int responseCount; + private int totalResults; + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public String getNextPageToken() { + return nextPageToken; + } + + public void setNextPageToken(String nextPageToken) { + this.nextPageToken = nextPageToken; + } + + public String getPrevPageToken() { + return prevPageToken; + } + + public void setPrevPageToken(String prevPageToken) { + this.prevPageToken = prevPageToken; + } + + public int getRequestCount() { + return requestCount; + } + + public void setRequestCount(int requestCount) { + this.requestCount = requestCount; + } + + public int getResponseCount() { + return responseCount; + } + + public void setResponseCount(int responseCount) { + this.responseCount = responseCount; + } + + public int getTotalResults() { + return totalResults; + } + + public void setTotalResults(int totalResults) { + this.totalResults = totalResults; + } + + public static class PageInfo implements Serializable { + private int totalResults; + private int resultsPerPage; + + public int getTotalResults() { + return totalResults; + } + + public void setTotalResults(int totalResults) { + this.totalResults = totalResults; + } + + public int getResultsPerPage() { + return resultsPerPage; + } + + public void setResultsPerPage(int resultsPerPage) { + this.resultsPerPage = resultsPerPage; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/bean/base/ResultBean.java b/app/src/main/java/net/oschina/app/improve/bean/base/ResultBean.java new file mode 100644 index 0000000000000000000000000000000000000000..5558f7193885b394dfcd9fccc1e58c4e433513c2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/base/ResultBean.java @@ -0,0 +1,83 @@ +package net.oschina.app.improve.bean.base; + +import net.oschina.app.improve.notice.NoticeBean; +import net.oschina.app.improve.notice.NoticeManager; + +/** + * Created by huanghaibin + * on 16-5-23. + */ +public class ResultBean { + public static final int RESULT_SUCCESS = 1; + public static final int RESULT_UNKNOW = 0; + public static final int RESULT_ERROR = -1; + public static final int RESULT_NOT_FIND = 404; + public static final int RESULT_NOT_LOGIN = 201; + public static final int RESULT_TOKEN_EXPRIED = 202; + public static final int RESULT_NOT_PERMISSION = 203; + public static final int RESULT_TOKEN_ERROR = 204; + + private T result; + private int code; + private String message; + private String time; + private NoticeBean notice; + + public T getResult() { + return result; + } + + public void setResult(T result) { + this.result = result; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getTime() { + return time; + } + + public void setTime(String time) { + this.time = time; + } + + public boolean isOk() { + return code == RESULT_SUCCESS; + } + + public boolean isSuccess() { + // 每次回来后通知消息到达 + NoticeManager.publish(this, this.notice); + return code == RESULT_SUCCESS && result != null; + } + + public NoticeBean getNotice() { + return notice; + } + + public void setNotice(NoticeBean notice) { + this.notice = notice; + } + + @Override + public String toString() { + return "code:" + code + + " + message:" + message + + " + time:" + time + + " + result:" + (result != null ? result.toString() : null); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/comment/Comment.java b/app/src/main/java/net/oschina/app/improve/bean/comment/Comment.java new file mode 100644 index 0000000000000000000000000000000000000000..4f8eff83138d168d7a725fb9a82279b17fa7d760 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/comment/Comment.java @@ -0,0 +1,125 @@ +package net.oschina.app.improve.bean.comment; + +import net.oschina.app.improve.bean.simple.Author; + +import java.io.Serializable; +import java.util.Arrays; + +/** + * Created by fei + * on 16/11/15. + * 评论实体,适用于所有评论(不包括软件评论,软件评论实际是一条动弹) + */ +public class Comment implements Serializable { + + public static final int VOTE_STATE_DEFAULT = 0; + public static final int VOTE_STATE_UP = 1; + public static final int VOTE_STATE_DOWN = 2; + + private long id; + private Author author; + private String content; + private String pubDate; + private int appClient; + private long vote; + private int voteState; + private boolean best; + private Refer[] refer; + private Reply[] reply; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getAppClient() { + return appClient; + } + + public void setAppClient(int appClient) { + this.appClient = appClient; + } + + public long getVote() { + return vote; + } + + public void setVote(long vote) { + this.vote = vote; + } + + public int getVoteState() { + return voteState; + } + + public void setVoteState(int voteState) { + this.voteState = voteState; + } + + public boolean isBest() { + return best; + } + + public void setBest(boolean best) { + this.best = best; + } + + public Refer[] getRefer() { + return refer; + } + + public void setRefer(Refer[] refer) { + this.refer = refer; + } + + public Reply[] getReply() { + return reply; + } + + public void setReply(Reply[] reply) { + this.reply = reply; + } + + @Override + public String toString() { + return "Comment{" + + "id=" + id + + ", author=" + author + + ", content='" + content + '\'' + + ", pubDate='" + pubDate + '\'' + + ", appClient=" + appClient + + ", vote=" + vote + + ", voteState=" + voteState + + ", best=" + best + + ", refer=" + Arrays.toString(refer) + + ", reply=" + Arrays.toString(reply) + + '}'; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/comment/Refer.java b/app/src/main/java/net/oschina/app/improve/bean/comment/Refer.java new file mode 100644 index 0000000000000000000000000000000000000000..69f99646f1f48848077cc3a6b8b977cea62e0ae4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/comment/Refer.java @@ -0,0 +1,48 @@ +package net.oschina.app.improve.bean.comment; + +import java.io.Serializable; + +/** + * Created by fei + * on 2016/11/15. + * desc:评论的引用,比如对他人评论的引用 + */ + +public class Refer implements Serializable { + private String author; + private String content; + private String pubDate; + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + @Override + public String toString() { + return "Refer{" + + "author='" + author + '\'' + + ", content='" + content + '\'' + + ", pubDate='" + pubDate + '\'' + + '}'; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/comment/Reply.java b/app/src/main/java/net/oschina/app/improve/bean/comment/Reply.java new file mode 100644 index 0000000000000000000000000000000000000000..2bc9aae1a9d2fc62cfb7cf7bad954476cf0fd4fd --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/comment/Reply.java @@ -0,0 +1,60 @@ +package net.oschina.app.improve.bean.comment; + +import net.oschina.app.improve.bean.simple.Author; + +import java.io.Serializable; + +/** + * Created by fei + * on 2016/11/15. + * desc: 对某一个引用评论的回复(评论) + */ + +public class Reply implements Serializable { + private long id; + private Author author; + private String content; + private String pubDate; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + @Override + public String toString() { + return "Reply{" + + "id=" + id + + ", author=" + author + + ", content='" + content + '\'' + + ", pubDate='" + pubDate + '\'' + + '}'; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/comment/Vote.java b/app/src/main/java/net/oschina/app/improve/bean/comment/Vote.java new file mode 100644 index 0000000000000000000000000000000000000000..66c66216086767d24ee5a3bc7e3ff600533ae41c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/comment/Vote.java @@ -0,0 +1,39 @@ +package net.oschina.app.improve.bean.comment; + +import java.io.Serializable; + +/** + * Created by fei + * on 2016/11/18. + * desc:vote + */ + +public class Vote implements Serializable { + + private long vote; + private int voteState; + + public long getVote() { + return vote; + } + + public void setVote(long vote) { + this.vote = vote; + } + + public int getVoteState() { + return voteState; + } + + public void setVoteState(int voteState) { + this.voteState = voteState; + } + + @Override + public String toString() { + return "Vote{" + + "vote=" + vote + + ", voteState=" + voteState + + '}'; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/resource/ImageResource.java b/app/src/main/java/net/oschina/app/improve/bean/resource/ImageResource.java new file mode 100644 index 0000000000000000000000000000000000000000..229d01caaf55cd69571720f2b2eb2da2c5599a6f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/resource/ImageResource.java @@ -0,0 +1,33 @@ +package net.oschina.app.improve.bean.resource; + +/** + * Created by JuQiu + * on 16/7/19. + */ +public class ImageResource { + private Image[] resources; + private String token; + + public Image[] getResources() { + return resources; + } + + public void setResources(Image[] resources) { + this.resources = resources; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public static class Image { + public String name; + public String thumb; + public String href; + public String type; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/shake/ShakeNews.java b/app/src/main/java/net/oschina/app/improve/bean/shake/ShakeNews.java new file mode 100644 index 0000000000000000000000000000000000000000..073f8b8dbb36a667b5a942a1214e6ccc97ca809a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/shake/ShakeNews.java @@ -0,0 +1,74 @@ +package net.oschina.app.improve.bean.shake; + +import java.io.Serializable; + +/** + * Created by haibin + * on 2016/10/12. + */ + +public class ShakeNews implements Serializable { + private String name; + private String detail; + private String href; + private long id; + private String img; + private String pubDate; + private int type; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDetail() { + return detail; + } + + public void setDetail(String detail) { + this.detail = detail; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getImg() { + return img; + } + + public void setImg(String img) { + this.img = img; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/shake/ShakePresent.java b/app/src/main/java/net/oschina/app/improve/bean/shake/ShakePresent.java new file mode 100644 index 0000000000000000000000000000000000000000..2dad7ecefecfba3912f1100358eafbaadf019224 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/shake/ShakePresent.java @@ -0,0 +1,38 @@ +package net.oschina.app.improve.bean.shake; + +import java.io.Serializable; + +/** + * Created by haibin + * on 2016/10/12. + */ + +public class ShakePresent implements Serializable { + private String name; + private String pic; + private String href; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPic() { + return pic; + } + + public void setPic(String pic) { + this.pic = pic; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/simple/About.java b/app/src/main/java/net/oschina/app/improve/bean/simple/About.java new file mode 100644 index 0000000000000000000000000000000000000000..85e5d6af7a9783867ca2e1f67204572d3319df63 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/simple/About.java @@ -0,0 +1,221 @@ +package net.oschina.app.improve.bean.simple; + +import android.text.TextUtils; + +import com.google.gson.annotations.Expose; + +import net.oschina.app.improve.bean.Tweet; + +import java.io.Serializable; +import java.util.Arrays; + +/** + * Created by JuQiu + * on 16/6/16. + * 相关推荐实体 + */ +public class About implements Serializable { + private long id; + private String title; + private String content; + private int type; + private String href; + private int viewCount; + private int commentCount; + private int transmitCount; + private Tweet.Image[] images; + @Expose + private long commitTweetId; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getViewCount() { + return viewCount; + } + + public void setViewCount(int viewCount) { + this.viewCount = viewCount; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public Tweet.Image[] getImages() { + return images; + } + + public void setImages(Tweet.Image[] images) { + this.images = images; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public int getTransmitCount() { + return transmitCount; + } + + public void setTransmitCount(int transmitCount) { + this.transmitCount = transmitCount; + } + + public long getCommitTweetId() { + return commitTweetId; + } + + public void setCommitTweetId(long commitTweetId) { + this.commitTweetId = commitTweetId; + } + + public static class Statistics implements Serializable { + private int comment; + private int view; + private int transmit; + + public int getComment() { + return comment; + } + + public void setComment(int comment) { + this.comment = comment; + } + + public int getView() { + return view; + } + + public void setView(int view) { + this.view = view; + } + + public int getTransmit() { + return transmit; + } + + public void setTransmit(int transmit) { + this.transmit = transmit; + } + } + + @Override + public String toString() { + return "About{" + + "id=" + id + + ", title='" + title + '\'' + + ", content='" + content + '\'' + + ", type=" + type + + ", href='" + href + '\'' + + ", viewCount=" + viewCount + + ", commentCount=" + commentCount + + ", transmitCount=" + transmitCount + + ", images=" + Arrays.toString(images) + + '}'; + } + + /** + * 检查一个About节点是否有效 + * + * @param about About + * @return True 则有效 + */ + public static boolean check(About about) { + return about != null + && !(about.id <= 0 && about.type <= 0 && TextUtils.isEmpty(about.href)); + } + + public static Share buildShare(About about) { + Share share = new Share(); + share.id = about.id; + share.type = about.type; + share.title = about.title; + share.content = about.content; + return share; + } + + public static Share buildShare(About about, long commitTweetId) { + Share share = buildShare(about); + share.commitTweetId = about.commitTweetId; + return share; + } + + public static Share buildShare(long id, int type) { + Share share = new Share(); + share.id = id; + share.type = type; + return share; + } + + /** + * 动弹分享节点 + */ + public static class Share implements Serializable { + public long id; + public int type; + public long commitTweetId; + public String title; + public String content; + public long fromTweetId; + + @Override + public String toString() { + return "Share{" + + "id=" + id + + ", type=" + type + + ", commitTweetId=" + commitTweetId + + ", title='" + title + '\'' + + ", content='" + content + '\'' + + ", fromTweetId=" + fromTweetId + + '}'; + } + } + + /** + * 检查About节点 + * + * @param share Share + * @return 返回分享节点是否正确 + */ + public static boolean check(Share share) { + return share != null && share.id > 0 && share.type >= 0; + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/bean/simple/Author.java b/app/src/main/java/net/oschina/app/improve/bean/simple/Author.java new file mode 100644 index 0000000000000000000000000000000000000000..6c2688793f456300a9a9af59e86b6bcc3094ab93 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/simple/Author.java @@ -0,0 +1,85 @@ +package net.oschina.app.improve.bean.simple; + +import java.io.Serializable; + +/** + * Created by huanghaibin_dev + * on 2016/7/18. + */ +public class Author implements Serializable { + protected long id; + protected String name; + protected String portrait; + protected int relation; + protected int gender; + private Identity identity; + + public Author() { + relation = 4; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPortrait() { + return portrait; + } + + public void setPortrait(String portrait) { + this.portrait = portrait; + } + + public int getRelation() { + return relation; + } + + public void setRelation(int relation) { + this.relation = relation; + } + + public int getGender() { + return gender; + } + + public void setGender(int gender) { + this.gender = gender; + } + + public Identity getIdentity() { + return identity; + } + + public void setIdentity(Identity identity) { + this.identity = identity; + } + + @Override + public String toString() { + return "Author{" + + "id=" + id + + ", name='" + name + '\'' + + ", portrait='" + portrait + '\'' + + ", relation=" + relation + + ", gender=" + gender + + ", identity=" + identity + + '}'; + } + + public static class Identity implements Serializable { + public boolean officialMember; + public boolean softwareAuthor; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/simple/Comment.java b/app/src/main/java/net/oschina/app/improve/bean/simple/Comment.java new file mode 100644 index 0000000000000000000000000000000000000000..9be843f5d113c4bb80539eb810a7cc3aaecda3c8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/simple/Comment.java @@ -0,0 +1,91 @@ +package net.oschina.app.improve.bean.simple; + +import java.io.Serializable; + +/** + * Created by JuQiu + * on 16/6/16. + * 评论实体 + */ + +public class Comment implements Serializable { + private long id; + private long authorId; + private String author; + private String authorPortrait; + private String content; + private String pubDate; + private int appClient; + private Refer refer; + + public static class Refer implements Serializable { + public String author; + public String content; + public String pubDate; + public Refer refer; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getAuthorId() { + return authorId; + } + + public void setAuthorId(long authorId) { + this.authorId = authorId; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getAuthorPortrait() { + return authorPortrait; + } + + public void setAuthorPortrait(String authorPortrait) { + this.authorPortrait = authorPortrait; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getAppClient() { + return appClient; + } + + public void setAppClient(int appClient) { + this.appClient = appClient; + } + + public Refer getRefer() { + return refer; + } + + public void setRefer(Refer refer) { + this.refer = refer; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/simple/CommentEX.java b/app/src/main/java/net/oschina/app/improve/bean/simple/CommentEX.java new file mode 100644 index 0000000000000000000000000000000000000000..c5f6487534978bc55cce20091aaa4e1b5eb7a661 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/simple/CommentEX.java @@ -0,0 +1,112 @@ +package net.oschina.app.improve.bean.simple; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/** + * Created by JuQiu + * on 16/6/16. + * 评论实体增强,适用于:问答模块 + */ +public class CommentEX extends Comment { + + public static final int VOTE_STATE_DEFAULT = 0; + public static final int VOTE_STATE_UP = 1; + public static final int VOTE_STATE_DOWN = 2; + + @SerializedName("vote") + private int voteCount; + private boolean best; + private int voteState; + private Reply[] reply; + + public static class Reply implements Serializable { + private long id; + private long authorId; + private String author; + private String content; + private String authorPortrait; + private String pubDate; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getAuthorId() { + return authorId; + } + + public void setAuthorId(long authorId) { + this.authorId = authorId; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getAuthorPortrait() { + return authorPortrait; + } + + public void setAuthorPortrait(String authorPortrait) { + this.authorPortrait = authorPortrait; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + } + + public int getVoteState() { + return voteState; + } + + public void setVoteState(int voteState) { + this.voteState = voteState; + } + + public Reply[] getReply() { + return reply; + } + + public void setReply(Reply[] reply) { + this.reply = reply; + } + + public int getVoteCount() { + return voteCount; + } + + public void setVoteCount(int voteCount) { + this.voteCount = voteCount; + } + + public boolean isBest() { + return best; + } + + public void setBest(boolean best) { + this.best = best; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/simple/Origin.java b/app/src/main/java/net/oschina/app/improve/bean/simple/Origin.java new file mode 100644 index 0000000000000000000000000000000000000000..b68336ed536a9796dbe52fbd3305c9778714fb51 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/simple/Origin.java @@ -0,0 +1,56 @@ +package net.oschina.app.improve.bean.simple; + +import java.io.Serializable; + +/** + * 来源:消息:AT我 + * Created by huanghaibin_dev + * on 2016/8/16. + */ +public class Origin implements Serializable { + public static final int ORIGIN_TYPE_LINK = 0; // 链接新闻 + public static final int ORIGIN_TYPE_SOFTWARE = 1; // 软件推荐 + public static final int ORIGIN_TYPE_DISCUSS = 2; // 讨论区帖子 + public static final int ORIGIN_TYPE_BLOG = 3; // 博客 + public static final int ORIGIN_TYPE_TRANSLATION = 4; // 翻译文章 + public static final int ORIGIN_TYPE_ACTIVE = 5; // 活动类型 + public static final int ORIGIN_TYPE_NEWS = 6; // 资讯类型 + public static final int ORIGIN_TYPE_TWEETS = 100; // 动弹 + + private long id; + private String desc; + private String href; + private int type; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/simple/SoftwareTweetLike.java b/app/src/main/java/net/oschina/app/improve/bean/simple/SoftwareTweetLike.java new file mode 100644 index 0000000000000000000000000000000000000000..b249086275de220157e451315d1a1cfe9a8db9db --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/simple/SoftwareTweetLike.java @@ -0,0 +1,38 @@ +package net.oschina.app.improve.bean.simple; + +import java.io.Serializable; + +/** + * Created by fei on 2016/7/19. + * desc: + */ + +public class SoftwareTweetLike implements Serializable { + + private Author author; + private boolean isLike; + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public boolean isLike() { + return isLike; + } + + public void setLike(boolean like) { + isLike = like; + } + + @Override + public String toString() { + return "SoftwareTweetLike{" + + "author=" + author + + ", isLike=" + isLike + + '}'; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/simple/TweetComment.java b/app/src/main/java/net/oschina/app/improve/bean/simple/TweetComment.java new file mode 100644 index 0000000000000000000000000000000000000000..355f3d29e7284a8279de0af7b63e4e0998bba825 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/simple/TweetComment.java @@ -0,0 +1,56 @@ +package net.oschina.app.improve.bean.simple; + +import java.io.Serializable; + +/** + * 动弹评论实体 + * Created by thanatos on 16/7/19. + */ +public class TweetComment implements Serializable { + + private long id; + private String content; + private String pubDate; + private int appClient; + private Author author; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getAppClient() { + return appClient; + } + + public void setAppClient(int appClient) { + this.appClient = appClient; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/simple/TweetLike.java b/app/src/main/java/net/oschina/app/improve/bean/simple/TweetLike.java new file mode 100644 index 0000000000000000000000000000000000000000..0d44c6712c7b1eb60367330e7ce357e4a6e458a6 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/simple/TweetLike.java @@ -0,0 +1,36 @@ +package net.oschina.app.improve.bean.simple; + +/** + * 点赞列表 + * Created by thanatos on 16/7/19. + */ +public class TweetLike { + + private String pubDate; + private Author author; + private boolean liked; + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public boolean isLiked() { + return liked; + } + + public void setLiked(boolean liked) { + this.liked = liked; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/simple/TweetLikeReverse.java b/app/src/main/java/net/oschina/app/improve/bean/simple/TweetLikeReverse.java new file mode 100644 index 0000000000000000000000000000000000000000..d69809446189e9a6eac4dfb19235e7764283f43e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/simple/TweetLikeReverse.java @@ -0,0 +1,36 @@ +package net.oschina.app.improve.bean.simple; + +/** + * 动弹赞和取消赞 + * Created by huanghaibin_dev + * on 2016/7/25. + */ +public class TweetLikeReverse { + private Author author; + private boolean liked; + private int likeCount; + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public boolean isLiked() { + return liked; + } + + public void setLiked(boolean liked) { + this.liked = liked; + } + + public int getLikeCount() { + return likeCount; + } + + public void setLikeCount(int likeCount) { + this.likeCount = likeCount; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/bean/simple/UserRelation.java b/app/src/main/java/net/oschina/app/improve/bean/simple/UserRelation.java new file mode 100644 index 0000000000000000000000000000000000000000..8c657de7706a38b98ba53347733b24924e3cf1f2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/bean/simple/UserRelation.java @@ -0,0 +1,41 @@ +package net.oschina.app.improve.bean.simple; + +import java.io.Serializable; + +/** + * Created by huanghaibin + * on 16-6-16. + */ +public class UserRelation implements Serializable { + public static final int RELATION_ALL = 1; + public static final int RELATION_ONLY_YOU = 2; + public static final int RELATION_ONLY_HER = 3; + public static final int RELATION_NONE = 4; + private int relation; + private String author; + private long authorId; + + public int getRelation() { + return relation; + } + + public void setRelation(int relation) { + this.relation = relation; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public long getAuthorId() { + return authorId; + } + + public void setAuthorId(long authorId) { + this.authorId = authorId; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/behavior/CommentBar.java b/app/src/main/java/net/oschina/app/improve/behavior/CommentBar.java new file mode 100644 index 0000000000000000000000000000000000000000..19928df041d89cf07ba3ae749624d0fc572a4b1d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/behavior/CommentBar.java @@ -0,0 +1,171 @@ +package net.oschina.app.improve.behavior; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.widget.BottomSheetBar; + +/** + * Created by haibin + * on 2016/11/10. + * Change by fei + * on 2016/11/17 + * desc:详情页输入框 + */ +@SuppressWarnings("all") +public class CommentBar { + + private Context mContext; + private View mRootView; + private FrameLayout mFrameLayout; + private ViewGroup mParent; + private ImageButton mFavView; + private FrameLayout mLinearComment; + private TextView mTextCommentCount; + private TextView mCommentText; + private BottomSheetBar mDelegation; + private LinearLayout mCommentLayout; + private LinearLayout mDispatchLayout; + private LinearLayout mLikeLayout; + private ImageView mImageLike; + + private CommentBar(Context context) { + this.mContext = context; + } + + public static CommentBar delegation(Context context, ViewGroup parent) { + CommentBar bar = new CommentBar(context); + bar.mRootView = LayoutInflater.from(context).inflate(R.layout.layout_comment_bar, parent, false); + bar.mParent = parent; + bar.mDelegation = BottomSheetBar.delegation(context); + bar.mParent.addView(bar.mRootView); + bar.initView(); + return bar; + } + + private void initView() { + //((CoordinatorLayout.LayoutParams) mRootView.getLayoutParams()).setBehavior(new FloatingAutoHideDownBehavior()); + mFavView = (ImageButton) mRootView.findViewById(R.id.ib_fav); + mLinearComment = (FrameLayout) mRootView.findViewById(R.id.fl_comment_count); + mCommentText = (TextView) mRootView.findViewById(R.id.tv_comment); + mTextCommentCount = (TextView) mRootView.findViewById(R.id.tv_comment_count); + mCommentLayout = (LinearLayout) mRootView.findViewById(R.id.ll_comment); + mDispatchLayout = (LinearLayout) mRootView.findViewById(R.id.ll_dispatch); + mLikeLayout = (LinearLayout) mRootView.findViewById(R.id.ll_like); + mImageLike = (ImageView)mRootView.findViewById(R.id.iv_thumbup) ; + mCommentLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (AccountHelper.isLogin()) { + mDelegation.show(mCommentText.getHint().toString()); + } else { + LoginActivity.show(mContext); + } + } + }); + } + + /** + * share 2 three sdk + * + * @param listener + */ + public void setCommentCountListener(View.OnClickListener listener) { + mLinearComment.setOnClickListener(listener); + } + + /** + * favorite the detail + * + * @param listener + */ + public void setFavListener(View.OnClickListener listener) { + mFavView.setOnClickListener(listener); + } + + public void setCommentListener(View.OnClickListener listener) { + mCommentText.setOnClickListener(listener); + } + + public void setCommentHint(String text) { + mCommentText.setHint(text); + } + + public void setFavDrawable(int drawable) { + mFavView.setImageResource(drawable); + } + + public BottomSheetBar getBottomSheet() { + return mDelegation; + } + + public void setCommitButtonEnable(boolean enable) { + mDelegation.getBtnCommit().setEnabled(enable); + } + + public void hideCommentCount() { + mLinearComment.setVisibility(View.GONE); + } + + public ImageView getLikeImage() { + return mImageLike; + } + + public void hideFav() { + mFavView.setVisibility(View.GONE); + } + + public void hideLike() { + mLikeLayout.setVisibility(View.GONE); + } + + public void hideDispatch() { + mDispatchLayout.setVisibility(View.GONE); + } + + public void showLike() { + mLikeLayout.setVisibility(View.VISIBLE); + } + + public void showDispatch() { + mDispatchLayout.setVisibility(View.VISIBLE); + } + + public void setLikeListener(View.OnClickListener likeListener){ + mLikeLayout.setOnClickListener(likeListener); + } + + + public void setDispatchListener(View.OnClickListener dispatchListener){ + mDispatchLayout.setOnClickListener(dispatchListener); + } + + public TextView getCommentText() { + return mCommentText; + } + + public TextView getCommentCountText() { + return mTextCommentCount; + } + + public void setCommentCount(int count) { + if (mTextCommentCount != null) { + mTextCommentCount.setText(String.valueOf(count)); + } + } + + public void performClick() { + mCommentLayout.performClick(); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/behavior/FloatingAutoHideDownBehavior.java b/app/src/main/java/net/oschina/app/improve/behavior/FloatingAutoHideDownBehavior.java new file mode 100644 index 0000000000000000000000000000000000000000..82d2e80dc891135af5a5875c5d3da8732194c57e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/behavior/FloatingAutoHideDownBehavior.java @@ -0,0 +1,167 @@ +package net.oschina.app.improve.behavior; + +import android.content.Context; +import android.support.design.widget.CoordinatorLayout; +import android.support.v4.view.ViewCompat; +import android.support.v4.widget.NestedScrollView; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; + +import net.oschina.app.R; +import net.oschina.app.util.TDevice; + +/** + * 滚动时隐藏的Behavior + * Created by thanatos on 16/2/17. + */ +public class FloatingAutoHideDownBehavior extends CoordinatorLayout.Behavior { + private static final Interpolator INTERPOLATOR = new DecelerateInterpolator(); + private boolean mIsAnimatingOut = false; + private boolean mIsScrollToBottom = false; + + public FloatingAutoHideDownBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FloatingAutoHideDownBehavior() { + super(); + } + + @Override + public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) { + super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); + +// if (!mIsScrollToBottom) { +// float mPreTranslationY = dy + child.getTranslationY(); +// if (mPreTranslationY <= 0) { +// child.setTranslationY(0); +// mIsAnimatingOut = true; +// } +// if (mPreTranslationY >= child.getHeight()) { +// child.setTranslationY(child.getHeight()); +// mIsAnimatingOut = false; +// } +// if (mPreTranslationY > 0 && mPreTranslationY < child.getHeight()) { +// child.setTranslationY(mPreTranslationY); +// mIsAnimatingOut = dy > 0; +// } +// } + } + + @Override + public boolean layoutDependsOn(CoordinatorLayout parent, final View child, View dependency) { + if (child != null && dependency != null && dependency instanceof NestedScrollView) { + NestedScrollView s = (NestedScrollView) dependency; + + if (s.getChildCount() > 0 && child.getHeight() > 0) { + View view = s.getChildAt(s.getChildCount() - 1); + if (view.getTag(R.id.detail_behavior_content_padding_done) == null) { + int paddingBottom = view.getPaddingBottom() + child.getHeight(); + view.setTag(R.id.detail_behavior_content_padding_done, paddingBottom); + view.setPadding(view.getPaddingLeft(), + view.getPaddingTop(), + view.getPaddingRight(), + paddingBottom); + } + } + + s.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() { + @Override + public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { + if (v.getChildCount() > 0) { + // Grab the last child placed in the ScrollView, we need it to determinate the bottom position. + View view = v.getChildAt(v.getChildCount() - 1); + // Calculate the scrolldiff + int diff = (view.getBottom() - (v.getHeight() + scrollY)); + // if diff is zero, then the bottom has been reached + if (diff == 0) { + // notify that we have reached the bottom + animateIn(child); + mIsScrollToBottom = true; + } else { + mIsScrollToBottom = false; + } + } + } + }); + } + return super.layoutDependsOn(parent, child, dependency); + } + + @Override + public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final View child, + final View directTargetChild, final View target, final int nestedScrollAxes) { + // 滑动时隐藏软键盘 + TDevice.hideSoftKeyboard(coordinatorLayout); + + // Ensure we react to vertical scrolling + return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL + || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); + } + + @Override + public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) { + super.onStopNestedScroll(coordinatorLayout, child, target); + + if (child.getTranslationY() == 0 || child.getTranslationY() == child.getHeight()) return; + + if (mIsAnimatingOut) { + animateOut(child); + } else { + animateIn(child); + } + + } + + private void animateOut(final View button) { +// button.animate() +// .translationY(button.getHeight()) +// .setInterpolator(INTERPOLATOR) +// .setDuration(200) +// .setListener(new AnimatorListenerAdapter() { +// @Override +// public void onAnimationEnd(Animator animation) { +// button.setTranslationY(button.getHeight()); +// } +// }); + } + + private void animateIn(final View button) { +// button.animate() +// .translationY(0) +// .setInterpolator(INTERPOLATOR) +// .setDuration(200) +// .setListener(new AnimatorListenerAdapter() { +// @Override +// public void onAnimationEnd(Animator animation) { +// button.setTranslationY(0); +// } +// }); + } + + + /** + * 点击内容栏唤起底部操作区域 + * + * @param coordinatorLayout 外部CoordinatorLayout + * @param contentView 滚动区域 + * @param bottomView 滚动时隐藏底部区域 + */ + public static void showBottomLayout(CoordinatorLayout coordinatorLayout, View contentView, final View bottomView) { + //coordinatorLayout.onStartNestedScroll(contentView, null, ViewCompat.SCROLL_AXIS_VERTICAL); + //coordinatorLayout.onNestedPreScroll(bottomView, 0, -1, new int[2]); + //coordinatorLayout.onStopNestedScroll(null); +// bottomView.animate() +// .translationY(0) +// .setInterpolator(INTERPOLATOR) +// .setDuration(200) +// .setListener(new AnimatorListenerAdapter() { +// @Override +// public void onAnimationEnd(Animator animation) { +// bottomView.setTranslationY(0); +// } +// }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/behavior/KeyboardActionDelegation.java b/app/src/main/java/net/oschina/app/improve/behavior/KeyboardActionDelegation.java new file mode 100644 index 0000000000000000000000000000000000000000..471c8098a6c0c3f939197dcf8bda09c7781cdae0 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/behavior/KeyboardActionDelegation.java @@ -0,0 +1,165 @@ +package net.oschina.app.improve.behavior; + +import android.content.Context; +import android.os.Handler; +import android.text.Spannable; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ImageView; + +import net.oschina.app.emoji.Emojicon; +import net.oschina.app.emoji.InputHelper; + +/** + * 键盘, emotion按钮, 输入框, emotion面板之间的相互关系委派给这个类管理 + * Created by thanatos on 3/4/16. + */ +public class KeyboardActionDelegation { + private ImageView mBtnEmotion; + private EditText mInput; + private Context mContext; + private ViewGroup mEmotionPanel; + + private boolean isShowSoftInput; + + // 事件回馈 + private OnActionChangeListener mOnActionChangeListener; + + private KeyboardActionDelegation(Context context, EditText input, ImageView button, ViewGroup view, OnActionChangeListener listener) { + this.mBtnEmotion = button; + this.mInput = input; + this.mContext = context; + this.mEmotionPanel = view; + this.mOnActionChangeListener = listener; + init(); + } + + public static KeyboardActionDelegation delegation(Context context, EditText input, ImageView button, ViewGroup view) { + return new KeyboardActionDelegation(context, input, button, view, null); + } + + + public static KeyboardActionDelegation delegation(Context context, EditText input, ImageView button, ViewGroup view, OnActionChangeListener listener) { + return new KeyboardActionDelegation(context, input, button, view, listener); + } + + /** + * 初始化, 绑定事件 + */ + private void init() { + mInput.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + hideEmotionPanel(); + } else { + hideSoftKeyboard(); + } + } + }); + + mInput.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!isEmotionPanelShowing()) return; + hideEmotionPanel(); + } + }); + + mBtnEmotion.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (isEmotionPanelShowing()) { + hideEmotionPanel(); + } else { + showEmotionPanel(); + } + } + }); + } + + public void showEmotionPanel() { + mBtnEmotion.setSelected(true); + hideSoftKeyboard(); + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + mEmotionPanel.setVisibility(View.VISIBLE); + + OnActionChangeListener listener = mOnActionChangeListener; + if (listener != null) { + listener.onShowEmotionPanel(KeyboardActionDelegation.this); + } + } + }, 300); + } + + private boolean isEmotionPanelShowing() { + return mEmotionPanel.getVisibility() == View.VISIBLE; + } + + /** + * 隐藏软键盘 + */ + private void hideSoftKeyboard() { + ((InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE)) + .hideSoftInputFromWindow(mInput.getWindowToken(), 0); + isShowSoftInput = false; + } + + /** + * 隐藏表情面板 + */ + private void hideEmotionPanel() { + mEmotionPanel.setVisibility(View.GONE); + mBtnEmotion.setSelected(false); + + OnActionChangeListener listener = mOnActionChangeListener; + if (listener != null) { + listener.onHideEmotionPanel(this); + } + } + + public boolean isShowSoftInput() { + return isShowSoftInput; + } + + public void onEmotionItemSelected(Emojicon emotion) { + if (mInput == null || emotion == null) { + return; + } + int start = mInput.getSelectionStart(); + int end = mInput.getSelectionEnd(); + if (start == end) { + mInput.append(InputHelper.displayEmoji(mContext.getResources(), emotion.getRemote())); + } else { + Spannable str = InputHelper.displayEmoji(mContext.getResources(), emotion.getRemote()); + mInput.getText().replace(Math.min(start, end), Math.max(start, end), str, 0, str.length()); + } + } + + /** + * 当使用回退键时 + * + * @return + */ + public boolean onTurnBack() { + if (isEmotionPanelShowing()) { + hideEmotionPanel(); + return false; + } + if (isShowSoftInput()) { + hideEmotionPanel(); + return false; + } + return true; + } + + public interface OnActionChangeListener { + void onHideEmotionPanel(KeyboardActionDelegation delegation); + + void onShowEmotionPanel(KeyboardActionDelegation delegation); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/behavior/KeyboardInputDelegation.java b/app/src/main/java/net/oschina/app/improve/behavior/KeyboardInputDelegation.java new file mode 100644 index 0000000000000000000000000000000000000000..cc12ff18755706975a13e6190415dfb56895602c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/behavior/KeyboardInputDelegation.java @@ -0,0 +1,290 @@ +package net.oschina.app.improve.behavior; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.design.widget.CoordinatorLayout; +import android.support.v4.app.FragmentManager; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.emoji.EmojiKeyboardFragment; +import net.oschina.app.emoji.Emojicon; +import net.oschina.app.emoji.InputHelper; +import net.oschina.app.emoji.OnEmojiClickListener; + +/** + * 底部操作的抽取 + * Created by thanatos on 16/6/21. + */ +@SuppressWarnings("all") +public class KeyboardInputDelegation { + + private Context context; + private boolean isLastEmpty = true; + + private CoordinatorLayout mCoorLayout; + private View mScrollerView; + private View mWrapperView; + + private EditText mViewInput; + private ImageView mViewShare; + private ImageView mViewFavor; + private ImageView mViewEmoji; + private ImageView mViewPic; + private FrameLayout mKeyboardFrame; + + private Button mBtnSend; + private LinearLayout mBottomLayout; + + private KeyboardActionDelegation mActionDelegation; + + private int mInputHeight; + private int mCurrentInputHeight; + + private KeyboardInputDelegation(Context context) { + this.context = context; + } + + public static KeyboardInputDelegation delegation(Context context, CoordinatorLayout mCoorLayout, View mScrollerView) { + KeyboardInputDelegation delegator = new KeyboardInputDelegation(context); + View view = LayoutInflater.from(context).inflate(R.layout.view_input_wrap, mCoorLayout, false); + delegator.setWrapperView(view); + delegator.setCoorLayout(mCoorLayout); + delegator.setScrollerView(mScrollerView); + mCoorLayout.addView(view); + return delegator; + } + + public void setBehavior(CoordinatorLayout.Behavior behavior) { + ((CoordinatorLayout.LayoutParams) mWrapperView.getLayoutParams()).setBehavior(behavior); + } + + @SuppressLint("ClickableViewAccessibility") + public void showEmoji(FragmentManager fragManager) { + if (mKeyboardFrame == null) + mKeyboardFrame = (FrameLayout) mWrapperView.findViewById(R.id.emoji_keyboard_fragment); + if (mViewEmoji == null) + mViewEmoji = (ImageView) mWrapperView.findViewById(R.id.iv_emoji); + mViewEmoji.setVisibility(View.VISIBLE); + + final EmojiKeyboardFragment mKeyboardFragment = new EmojiKeyboardFragment(); + mKeyboardFragment.setDelegate(true); + mKeyboardFragment.setOnEmojiClickListener(new OnEmojiClickListener() { + @Override + public void onDeleteButtonClick(View v) { + InputHelper.backspace(mViewInput); + } + + @Override + public void onEmojiClick(Emojicon v) { + mActionDelegation.onEmotionItemSelected(v); + } + }); + mCoorLayout.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + onTurnBack(); + } + return false; + } + }); + + fragManager.beginTransaction() + .replace(R.id.emoji_keyboard_fragment, mKeyboardFragment) + .commit(); + + mActionDelegation = KeyboardActionDelegation.delegation(context, mViewInput, mViewEmoji, mKeyboardFrame, new KeyboardActionDelegation.OnActionChangeListener() { + @Override + public void onHideEmotionPanel(KeyboardActionDelegation delegation) { + mKeyboardFragment.hideEmojiKeyBoard(); + } + + @Override + public void onShowEmotionPanel(KeyboardActionDelegation delegation) { + mKeyboardFragment.showEmojiKeyBoard(); + } + }); + } + + private void hideSendButton() { + if (mBottomLayout == null) { + mBottomLayout = (LinearLayout) mWrapperView.findViewById(R.id.ll_bottom); + + } + mBottomLayout.setVisibility(View.VISIBLE); + mBtnSend.setVisibility(View.GONE); + } + + private void showSendButton() { + if (mBottomLayout == null) { + mBottomLayout = (LinearLayout) mWrapperView.findViewById(R.id.ll_bottom); + } + mBottomLayout.setVisibility(View.GONE); + mBtnSend.setVisibility(View.VISIBLE); + } + + public void showShare(View.OnClickListener l) { + if (mViewShare == null) + mViewShare = (ImageView) mWrapperView.findViewById(R.id.iv_share); + if (l != null) mViewShare.setOnClickListener(l); + mViewShare.setVisibility(View.VISIBLE); + } + + public void showFavor(View.OnClickListener l) { + if (mViewFavor == null) + mViewFavor = (ImageView) mWrapperView.findViewById(R.id.iv_fav); + if (l != null) mViewFavor.setOnClickListener(l); + mViewFavor.setVisibility(View.VISIBLE); + } + + public void setFavorDrawable(int drawable) { + mViewFavor.setImageResource(drawable); + } + + public void showPic(View.OnClickListener l) { + if (mViewPic == null) { + mViewPic = (ImageView) mWrapperView.findViewById(R.id.iv_pic); + } + if (l != null) mViewPic.setOnClickListener(l); + mViewPic.setVisibility(View.VISIBLE); + } + + private void setWrapperView(View view) { + this.mWrapperView = view; + mViewInput = (EditText) this.mWrapperView.findViewById(R.id.et_input); + mViewInput.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + if (mInputHeight == 0) { + mInputHeight = mViewInput.getMeasuredHeight(); + } + } + }); + mBtnSend = (Button) mWrapperView.findViewById(R.id.btn_send); + } + + private void setCoorLayout(CoordinatorLayout view) { + this.mCoorLayout = view; + } + + private void setScrollerView(View view) { + this.mScrollerView = view; + } + + public void setAdapter(final KeyboardInputAdapter mInputAdapter) { + mViewInput.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_SEND) { +// InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); +// imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + mInputAdapter.onSubmit(v, v.getText().toString()); + return true; + } + return false; + } + }); + mViewInput.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) { + mInputAdapter.onBackSpace(v); + if (!TextUtils.isEmpty(mViewInput.getText().toString())) { + isLastEmpty = false; + return false; + } + if (TextUtils.isEmpty(mViewInput.getText().toString()) && !isLastEmpty) { + isLastEmpty = true; + return false; + } + mInputAdapter.onFinalBackSpace(v); + return true; + } + return false; + } + }); + + } + + public void setSendListener(View.OnClickListener listener) { + mBtnSend.setOnClickListener(listener); + mViewInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (!TextUtils.isEmpty(s.toString().replace("\n", "").replace(" ", ""))) { + showSendButton(); + } else { + hideSendButton(); + } + } + + @Override + public void afterTextChanged(Editable s) { + mCurrentInputHeight = mViewInput.getMeasuredHeight(); + } + }); + mViewInput.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + if (TextUtils.isEmpty(getInputText())) { + mViewInput.setText(""); + } + mViewInput.getLayoutParams().height = mInputHeight; + mViewInput.requestLayout(); + } else { + if (mCurrentInputHeight >= mInputHeight) { + mViewInput.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT; + mViewInput.requestLayout(); + } + } + } + }); + } + + public EditText getInputView() { + return mViewInput; + } + + public String getInputText() { + return mViewInput.getText().toString().trim(); + } + + public void notifyWrapper() { + FloatingAutoHideDownBehavior.showBottomLayout(mCoorLayout, mScrollerView, mWrapperView); + } + + private boolean onTurnBack() { + return mActionDelegation == null || mActionDelegation.onTurnBack(); + } + + public static abstract class KeyboardInputAdapter { + public abstract void onSubmit(TextView v, String content); + + public void onBackSpace(View v) { + } + + public void onFinalBackSpace(View v) { + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/comment/CommentExsActivity.java b/app/src/main/java/net/oschina/app/improve/comment/CommentExsActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..e099cdd9740f49a076a306be594e5dbd1edbc63b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/comment/CommentExsActivity.java @@ -0,0 +1,410 @@ +package net.oschina.app.improve.comment; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.design.widget.CoordinatorLayout; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.bumptech.glide.RequestManager; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BaseBackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.CommentEX; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.improve.widget.RecyclerRefreshLayout; +import net.oschina.app.improve.widget.adapter.OnKeyArrivedListenerAdapterV2; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; +import net.oschina.app.widget.TweetTextView; + +import java.lang.reflect.Type; +import java.util.List; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +public class CommentExsActivity extends BaseBackActivity { + private long mId; + private int mType; + + private PageBean mPageBean; + + @Bind(R.id.lay_refreshLayout) + RecyclerRefreshLayout mRefreshLayout; + + @Bind(R.id.lay_blog_detail_comment) + RecyclerView mLayComments; + + @Bind(R.id.activity_comments) + CoordinatorLayout mCoorLayout; + + private Adapter mAdapter; + private Comment reply; + private CommentBar mDelegation; + private View.OnClickListener onReplyBtnClickListener; + + public static void show(Context context, long id, int type) { + Intent intent = new Intent(context, CommentExsActivity.class); + intent.putExtra("id", id); + intent.putExtra("type", type); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_comments; + } + + @Override + protected boolean initBundle(Bundle bundle) { + mId = bundle.getLong("id"); + mType = bundle.getInt("type"); + return super.initBundle(bundle); + } + + @Override + protected void initWidget() { + super.initWidget(); + LinearLayoutManager manager = new LinearLayoutManager(this); + mLayComments.setLayoutManager(manager); + + mAdapter = new Adapter(this); + mLayComments.setAdapter(mAdapter); + + mDelegation = CommentBar.delegation(this, mCoorLayout); + mDelegation.getBottomSheet().setCommitListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + handleSendComment(mId, reply == null ? 0 : reply.getId(), reply == null ? 0 : reply.getAuthor().getId(), mDelegation.getBottomSheet().getCommentText()); + } + }); + + mDelegation.hideFav(); + mDelegation.hideCommentCount(); + + mDelegation.getBottomSheet().setMentionListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (AccountHelper.isLogin()) { + UserSelectFriendsActivity.show(CommentExsActivity.this, mDelegation.getBottomSheet().getEditText()); + } else { + LoginActivity.show(CommentExsActivity.this); + } + } + }); + + mDelegation.getBottomSheet().getEditText().setOnKeyArrivedListener(new OnKeyArrivedListenerAdapterV2(this)); + mDelegation.getBottomSheet().getEditText().setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) { + if (reply == null) return false; + reply = null; + mDelegation.getCommentText().setHint("发表评论"); + mDelegation.getBottomSheet().getEditText().setHint("发表评论"); + } + return false; + } + }); + mRefreshLayout.setColorSchemeResources( + R.color.swiperefresh_color1, R.color.swiperefresh_color2, + R.color.swiperefresh_color3, R.color.swiperefresh_color4); + } + + @Override + protected void initData() { + super.initData(); + mRefreshLayout.setSuperRefreshLayoutListener(new RecyclerRefreshLayout.SuperRefreshLayoutListener() { + @Override + public void onRefreshing() { + getData(true, null); + } + + @Override + public void onLoadMore() { + String token = null; + if (mPageBean != null) + token = mPageBean.getNextPageToken(); + getData(false, token); + } + + @Override + public void onScrollToBottom() { + + } + }); + + mRefreshLayout.post(new Runnable() { + @Override + public void run() { + mRefreshLayout.setRefreshing(true); + mRefreshLayout.onRefresh(); + } + }); + } + + /** + * 检查当前数据,并检查网络状况 + * + * @return 返回当前登录用户, 未登录或者未通过检查返回0 + */ + private long requestCheck() { + if (mId == 0) { + AppContext.showToast("数据加载中..."); + return 0; + } + if (!TDevice.hasInternet()) { + AppContext.showToastShort(R.string.tip_no_internet); + return 0; + } + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(this); + return 0; + } + // 返回当前登录用户ID + return AccountHelper.getUserId(); + } + + + /** + * handle send comment + */ + private void handleSendComment(long id, final long commentId, long commentAuthorId, String content) { + long uid = requestCheck(); + if (uid == 0) + return; + + if (TextUtils.isEmpty(content)) { + AppContext.showToastShort(R.string.tip_comment_content_empty); + return; + } + OSChinaApi.pubQuestionComment(id, commentId, commentAuthorId, content, new TextHttpResponseHandler() { + @Override + public void onStart() { + super.onStart(); + if (mDelegation == null) return; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(false); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + AppContext.showToastShort("评论失败!"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + CommentEX respComment = resultBean.getResult(); + if (respComment != null) { + AppContext.showToastShort("评论成功"); + mDelegation.setCommentHint("发表评论"); + mDelegation.getBottomSheet().getEditText().setHint("发表评论"); + getData(true, null); + mDelegation.getBottomSheet().dismiss(); + } + } else { + AppContext.showToastShort(resultBean.getMessage()); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + + @Override + public void onFinish() { + super.onFinish(); + if (mDelegation == null) return; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(true); + } + }); + + } + + @SuppressWarnings("deprecation") + private void getData(final boolean clearData, String token) { + OSChinaApi.getComments(mId, mType, "refer", token, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + + } + + @Override + public void onFinish() { + super.onFinish(); + mRefreshLayout.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + + ResultBean> resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean != null && resultBean.isSuccess()) { + if (resultBean.getResult() != null + && resultBean.getResult().getItems() != null + && resultBean.getResult().getItems().size() > 0) { + mPageBean = resultBean.getResult(); + handleData(mPageBean.getItems(), clearData); + return; + } + } + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + }); + } + + private void handleData(List comments, boolean clearData) { + if (clearData) + mAdapter.clear(); + + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, false); + mAdapter.addAll(comments); + if (mAdapter.getItems().size() < 20) + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + } + + public View.OnClickListener getReplyBtnClickListener() { + if (onReplyBtnClickListener == null) { + onReplyBtnClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + Comment comment = (Comment) v.getTag(); + mDelegation.setCommentHint("@" + comment.getAuthor() + " :"); + mDelegation.getBottomSheet().getEditText().setHint("@" + comment.getAuthor() + " :"); + reply = comment; + } + }; + } + return onReplyBtnClickListener; + } + + private static class CommentHolder extends RecyclerView.ViewHolder { + private PortraitView mAvatar; + private IdentityView mIdentityView; + private TextView mName; + private TextView mDate; + private TweetTextView mContent; + private LinearLayout mRefers; + private ImageView btn_comment, iv_best_comment; + + CommentHolder(View itemView) { + super(itemView); + + mAvatar = (PortraitView) itemView.findViewById(R.id.iv_avatar); + mName = (TextView) itemView.findViewById(R.id.tv_name); + mDate = (TextView) itemView.findViewById(R.id.tv_pub_date); + btn_comment = (ImageView) itemView.findViewById(R.id.btn_comment); + iv_best_comment = (ImageView) itemView.findViewById(R.id.iv_best_answer); + mContent = ((TweetTextView) itemView.findViewById(R.id.tv_content)); + mRefers = ((LinearLayout) itemView.findViewById(R.id.lay_refer)); + } + + void setData(Comment comment, RequestManager imageLoader, View.OnClickListener l) { + Author author = comment.getAuthor(); + if (author != null) { + mAvatar.setup(author); + } else { + mAvatar.setup(0, "匿名用户", ""); + } + mIdentityView.setup(author); + + mName.setText(author == null ? "匿名用户" : author.getName()); + mDate.setText(comment.getPubDate()); + CommentsUtil.formatHtml(mContent.getResources(), mContent, comment.getContent()); + + mRefers.removeAllViews(); + if (comment.getRefer() != null) { + // 最多5层 + View view = CommentsUtil.getReplyLayout(LayoutInflater.from(mRefers.getContext()), comment.getReply(), 0); + mRefers.addView(view); + } + + btn_comment.setTag(comment); + if (l != null) + btn_comment.setOnClickListener(l); + } + } + + private class Adapter extends BaseRecyclerAdapter { + + Adapter(Context context) { + super(context, ONLY_FOOTER); + mState = STATE_LOADING; + setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + CommentExsActivity.this.onItemClick(getItem(position)); + } + }); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.lay_comment_item_ex, parent, false); + return new CommentHolder(view); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Comment item, int position) { + if (holder instanceof CommentHolder) { + CommentHolder commentHolder = (CommentHolder) holder; + RequestManager requestManager = getImageLoader(); + if (requestManager != null) + commentHolder.setData(item, requestManager, null); + if (item.isBest()) { + commentHolder.btn_comment.setVisibility(View.GONE); + commentHolder.iv_best_comment.setVisibility(View.VISIBLE); + } else { + commentHolder.btn_comment.setVisibility(View.VISIBLE); + commentHolder.iv_best_comment.setVisibility(View.GONE); + } + } + } + + } + + private void onItemClick(Comment comment) { + QuesAnswerDetailActivity.show(this, comment, mId, mType); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/comment/CommentReferView.java b/app/src/main/java/net/oschina/app/improve/comment/CommentReferView.java new file mode 100644 index 0000000000000000000000000000000000000000..9999edd9a5befc987a0c1bbe5c6cce910e32d533 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/comment/CommentReferView.java @@ -0,0 +1,51 @@ +package net.oschina.app.improve.comment; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; + +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.comment.Refer; + +/** + * Created by fei + * on 2016/11/21. + * desc: + */ + +public class CommentReferView extends LinearLayout { + + public CommentReferView(Context context) { + super(context); + initView(); + } + + public CommentReferView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initView(); + } + + public CommentReferView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(); + } + + private void initView() { + setOrientation(VERTICAL); + } + + + public void addComment(final Comment comment) { + removeAllViews();//因为在list中有复用问题,不同的refers长度不同,并且不一样,所以需要先清除掉原先的布局,包括 + Refer[] refers = comment.getRefer(); + + if (refers != null && refers.length > 0) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + View child = CommentsUtil.getReferLayout(inflater, refers, 0); + addView(child, indexOfChild(child)); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/comment/CommentView.java b/app/src/main/java/net/oschina/app/improve/comment/CommentView.java new file mode 100644 index 0000000000000000000000000000000000000000..3f9277b352b9ced8bb59cc154a217ff6a9a2a36a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/comment/CommentView.java @@ -0,0 +1,373 @@ +package net.oschina.app.improve.comment; + +import android.annotation.SuppressLint; +import android.app.ProgressDialog; +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.bumptech.glide.RequestManager; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.comment.Refer; +import net.oschina.app.improve.bean.comment.Vote; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; +import net.oschina.app.widget.TweetTextView; +import net.oschina.common.utils.CollectionUtil; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by fei + * on 2016/11/16. + * desc: 资讯、问答、博客、翻译、活动、软件详情评论列表当中进行展示的子view. + * 包括直接渲染出评论下的refer和reply + */ +public class CommentView extends LinearLayout implements View.OnClickListener { + + private long mId; + private int mType; + private TextView mTitle; + private String mShareTitle; + private TextView mSeeMore; + private LinearLayout mLayComments; + private ProgressDialog mDialog; + private CommentBar commentBar; + + public CommentView(Context context) { + super(context); + init(); + } + + public CommentView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public CommentView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + setOrientation(VERTICAL); + LayoutInflater inflater = LayoutInflater.from(getContext()); + inflater.inflate(R.layout.lay_detail_comment_layout, this, true); + mTitle = (TextView) findViewById(R.id.tv_blog_detail_comment); + mLayComments = (LinearLayout) findViewById(R.id.lay_detail_comment); + mSeeMore = (TextView) findViewById(R.id.tv_see_more_comment); + } + + public void setTitle(String title) { + if (!TextUtils.isEmpty(title)) { + mTitle.setText(title); + } + } + + public void setShareTitle(String shareTitle) { + this.mShareTitle = shareTitle; + } + + public void setCommentBar(CommentBar commentBar) { + this.commentBar = commentBar; + } + + /** + * @return TypeToken + */ + Type getCommentType() { + return new TypeToken>>() { + }.getType(); + } + + /** + * @return TypeToken + */ + Type getVoteType() { + return new TypeToken>() { + }.getType(); + } + + public void init(long id, final int type, int order, final int commentCount, final RequestManager imageLoader, + final OnCommentClickListener onCommentClickListener) { + this.mId = id; + this.mType = type; + + mSeeMore.setVisibility(View.GONE); + mSeeMore.setText(String.format("查看所有 %s 条评论",commentCount)); + setVisibility(GONE); + + OSChinaApi.getComments(id, type, "refer,reply", order, null, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (throwable != null) + throwable.printStackTrace(); + } + + @SuppressLint("DefaultLocale") + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + + ResultBean> resultBean = AppOperator.createGson().fromJson(responseString, getCommentType()); + if (resultBean.isSuccess()) { + + List comments = resultBean.getResult().getItems(); + + int size = comments.size(); + if (type == OSChinaApi.COMMENT_NEWS || + type == OSChinaApi.COMMENT_TRANSLATION || + type == OSChinaApi.COMMENT_BLOG || + type == OSChinaApi.COMMENT_SOFT) { + List hotComments = new ArrayList<>(); + hotComments.clear(); + //筛选出热门评论 + for (int i = 0, len = comments.size(); i < (len > 5 ? 5 : len); i++) { + Comment comment = comments.get(i); +// if (comment.getVote() > 0) { +// +// } + hotComments.add(comment); + } + comments = hotComments; + int len = comments.size(); + if (len > 0) { + //表示热门评论数目 + setTitle(String.format("%s", getResources().getString(R.string.hot_comment_hint))); + mSeeMore.setVisibility(VISIBLE); + mSeeMore.setOnClickListener(CommentView.this); + } + } else if (commentCount > size) { + mSeeMore.setVisibility(VISIBLE); + mSeeMore.setOnClickListener(CommentView.this); + } + + Comment[] array = CollectionUtil.toArray(comments, Comment.class); + initComment(array, imageLoader, onCommentClickListener); + } + + } catch (Exception e) { + onFailure(statusCode, headers, responseString, e); + } + } + }); + } + + private void initComment(final Comment[] comments, final RequestManager imageLoader, final OnCommentClickListener onCommentClickListener) { + if (mLayComments != null) + mLayComments.removeAllViews(); + if (comments != null && comments.length > 0) { + if (getVisibility() != VISIBLE) { + setVisibility(VISIBLE); + } + + for (int i = 0, len = comments.length; i < len; i++) { + final Comment comment = comments[i]; + if (comment != null) { + final ViewGroup lay = insertComment(true, comment, imageLoader, onCommentClickListener); + lay.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mType == OSChinaApi.COMMENT_EVENT || mType == OSChinaApi.COMMENT_QUESTION) { + QuesAnswerDetailActivity.show(lay.getContext(), comment, mId, mType); + } else { + onCommentClickListener.onClick(v, comment); + } + } + }); + + mLayComments.addView(lay); + if (i == len - 1) { + lay.findViewById(R.id.line).setVisibility(GONE); + } else { + lay.findViewById(R.id.line).setVisibility(View.VISIBLE); + } + } + } + } else { + setVisibility(View.GONE); + } + } + + + @SuppressLint("DefaultLocale") + private ViewGroup insertComment(final boolean first, final Comment comment, final RequestManager imageLoader, + final OnCommentClickListener onCommentClickListener) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + @SuppressLint("InflateParams") ViewGroup lay = (ViewGroup) inflater.inflate(R.layout.lay_comment_item, null, false); + + IdentityView identityView = (IdentityView) lay.findViewById(R.id.identityView); + PortraitView ivAvatar = (PortraitView) lay.findViewById(R.id.iv_avatar); + identityView.setup(comment.getAuthor()); + ivAvatar.setup(comment.getAuthor()); + ivAvatar.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + OtherUserHomeActivity.show(getContext(), comment.getAuthor().getId()); + } + }); + final TextView tvVoteCount = (TextView) lay.findViewById(R.id.tv_vote_count); + tvVoteCount.setText(String.valueOf(comment.getVote())); + final ImageView ivVoteStatus = (ImageView) lay.findViewById(R.id.btn_vote); + +// commentBar.getBottomSheet().show(String.format("%s %s", +// ivComment.getResources().getString(R.string.reply_hint), comment.getAuthor().getName())); + + if (mType == OSChinaApi.COMMENT_QUESTION || mType == OSChinaApi.COMMENT_EVENT + || mType == OSChinaApi.COMMENT_TRANSLATION || mType == OSChinaApi.COMMENT_BLOG) { + + tvVoteCount.setVisibility(View.GONE); + ivVoteStatus.setVisibility(View.GONE); + if (comment.isBest()) { +// ivComment.setEnabled(false); +// ivComment.setImageResource(R.mipmap.label_best_answer); + } else { +// ivComment.setEnabled(true); +// ivComment.setImageResource(R.mipmap.ic_comment_30); + } + } else { + //ivComment.setEnabled(true); + tvVoteCount.setText(String.valueOf(comment.getVote())); + tvVoteCount.setVisibility(View.VISIBLE); + ivVoteStatus.setVisibility(View.VISIBLE); + + if (comment.getVoteState() == 1) { + ivVoteStatus.setImageResource(R.mipmap.ic_thumbup_actived); + ivVoteStatus.setTag(true); + } else if (comment.getVoteState() == 0) { + ivVoteStatus.setImageResource(R.mipmap.ic_thumb_normal); + ivVoteStatus.setTag(null); + } + + ivVoteStatus.setOnClickListener(new OnClickListener() { + @Override + public void onClick(final View v) { + handVote(); + } + + private void handVote() { + if (ivVoteStatus.getTag() != null || comment.getVoteState() == 1) { + return; + } + if (!AccountHelper.isLogin()) { + LoginActivity.show(getContext()); + return; + } + if (!TDevice.hasInternet()) { + AppContext.showToast(getResources().getString(R.string.state_network_error), Toast.LENGTH_SHORT); + return; + } + + OSChinaApi.voteComment(mType, comment.getId(), comment.getAuthor().getId(), 1, new TextHttpResponseHandler() { + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + requestFailureHint(throwable); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, getVoteType()); + if (resultBean.isSuccess()) { + Vote vote = resultBean.getResult(); + if (vote != null) { + if (vote.getVoteState() == 1) { + comment.setVoteState(1); + ivVoteStatus.setTag(true); + ivVoteStatus.setImageResource(R.mipmap.ic_thumbup_actived); + } else if (vote.getVoteState() == 0) { + comment.setVoteState(0); + ivVoteStatus.setTag(null); + ivVoteStatus.setImageResource(R.mipmap.ic_thumb_normal); + } + long voteVoteCount = vote.getVote(); + comment.setVote(voteVoteCount); + tvVoteCount.setText(String.valueOf(voteVoteCount)); + } + AppContext.showToastShort("操作成功!!!"); + } else { + AppContext.showToastShort(resultBean.getMessage()); + } + } + + }); + } + }); + } + + String name = comment.getAuthor().getName(); + if (TextUtils.isEmpty(name)) { + name = getResources().getString(R.string.martian_hint); + } + + ((TextView) lay.findViewById(R.id.tv_name)).setText(name); + + ((TextView) lay.findViewById(R.id.tv_pub_date)).setText( + String.format("%s", StringUtils.formatSomeAgo(comment.getPubDate()))); + + TweetTextView content = ((TweetTextView) lay.findViewById(R.id.tv_content)); + CommentsUtil.formatHtml(getResources(), content, comment.getContent()); + Refer[] refers = comment.getRefer(); + + if (refers != null && refers.length > 0) { + View view = CommentsUtil.getReferLayout(inflater, refers, 0); + lay.addView(view, lay.indexOfChild(content)); + } + + if (!first) { + addView(lay, 0); + } + + return lay; + } + + @Override + public void onClick(View v) { + if (mId != 0 && mType != 0) + CommentsActivity.show((AppCompatActivity) getContext(), mId, mType, OSChinaApi.COMMENT_NEW_ORDER, mShareTitle); + } + + /** + * request network error + * + * @param throwable throwable + */ + protected void requestFailureHint(Throwable throwable) { + AppContext.showToastShort(R.string.request_error_hint); + if (throwable != null) { + throwable.printStackTrace(); + } + } +} + + + diff --git a/app/src/main/java/net/oschina/app/improve/comment/CommentsActivity.java b/app/src/main/java/net/oschina/app/improve/comment/CommentsActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..9530a1ce1bbded564b8830120293cd226c72937a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/comment/CommentsActivity.java @@ -0,0 +1,557 @@ +package net.oschina.app.improve.comment; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.comment.adapter.CommentAdapter; +import net.oschina.app.improve.tweet.service.TweetPublishService; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.CommentShareView; +import net.oschina.app.improve.widget.RecyclerRefreshLayout; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.improve.widget.adapter.OnKeyArrivedListenerAdapterV2; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; + +import java.lang.reflect.Type; +import java.util.List; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * Created by fei + * on 16/11/17 + * desc:详情评论列表ui + */ +public class CommentsActivity extends BackActivity implements + BaseRecyclerAdapter.OnItemLongClickListener, + EasyPermissions.PermissionCallbacks { + + @Bind(R.id.lay_refreshLayout) + RecyclerRefreshLayout mRefreshLayout; + + @Bind(R.id.lay_blog_detail_comment) + RecyclerView mLayComments; + + @Bind(R.id.activity_comments) + LinearLayout mCoordinatorLayout; + + + @Bind(R.id.shareView) + CommentShareView mShareView; + private CommentAdapter mCommentAdapter; + + private CommentBar mDelegation; + private boolean mInputDoubleEmpty = true; + private boolean isAddCommented; + + private TextHttpResponseHandler mHandler = new TextHttpResponseHandler() { + + @Override + public void onStart() { + super.onStart(); + if (mDelegation == null) return; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(false); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (isDestroy()) + return; + AppContext.showToastShort(getResources().getString(R.string.pub_comment_failed)); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (isDestroy()) + return; + try { + Type type = new TypeToken>() { + }.getType(); + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + isAddCommented = true; + Comment respComment = resultBean.getResult(); + if (respComment != null) { + handleSyncTweet(); + mDelegation.setCommentHint(getString(mSourceId)); + mDelegation.getBottomSheet().getEditText().setHint(getString(mSourceId)); + AppContext.showToastShort(getString(R.string.pub_comment_success)); + mDelegation.getBottomSheet().getEditText().setText(""); + mDelegation.getBottomSheet().getBtnCommit().setTag(null); + mDelegation.getBottomSheet().dismiss(); + getData(true, null); + } + } else { + AppContext.showToastShort(resultBean.getMessage()); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + + @Override + public void onFinish() { + super.onFinish(); + if (isDestroy()) + return; + if (mDelegation == null) return; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(true); + } + }; + + private int mOrder; + private int mSourceId; + + private long mId; + private int mType; + + private PageBean mPageBean; + private AlertDialog mShareCommentDialog; + private Comment mComment; + private String mShareTitle; + + public static void show(Activity activity, long id, int type, int order, String title) { + Intent intent = new Intent(activity, CommentsActivity.class); + intent.putExtra("id", id); + intent.putExtra("type", type); + intent.putExtra("order", order); + intent.putExtra("title", title); + activity.startActivityForResult(intent, 1); + } + + @Override + protected int getContentView() { + return R.layout.activity_comments; + } + + @Override + protected boolean initBundle(Bundle bundle) { + mId = bundle.getLong("id"); + mType = bundle.getInt("type"); + mOrder = bundle.getInt("order"); + mShareTitle = bundle.getString("title"); + return super.initBundle(bundle); + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mShareCommentDialog = DialogHelper.getRecyclerViewDialog(this, new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + switch (position) { + case 0: + TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(mComment.getContent())); + break; + case 1: + if (!AccountHelper.isLogin()) { + LoginActivity.show(CommentsActivity.this, 1); + return; + } + if (mComment.getAuthor() == null || + mComment.getAuthor().getId() == 0) { + SimplexToast.show(CommentsActivity.this,"不能回复游客..."); + return; + } + mDelegation.getBottomSheet().getBtnCommit().setTag(mComment); + + mDelegation.getBottomSheet().show(String.format("%s %s", + getString(R.string.reply_hint), mComment.getAuthor().getName())); + break; + case 2: + mShareView.init(mShareTitle, mComment); + //mShareView.share(); + saveToFileByPermission(); + break; + } + mShareCommentDialog.dismiss(); + } + }).create(); + + + mDelegation = CommentBar.delegation(this, mCoordinatorLayout); + mSourceId = R.string.pub_comment_hint; + if (mType == OSChinaApi.COMMENT_QUESTION) { + mSourceId = R.string.answer_hint; + } + if (mType == OSChinaApi.COMMENT_EVENT) { + mSourceId = R.string.comment_hint; + } + mDelegation.getBottomSheet().getEditText().setHint(mSourceId); + mDelegation.hideFav(); + mDelegation.hideCommentCount(); + + if (mType == OSChinaApi.COMMENT_SOFT) { + mDelegation.getBottomSheet().hideSyncAction(); + } + + mDelegation.getBottomSheet().setMentionListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (AccountHelper.isLogin()) { + UserSelectFriendsActivity.show(CommentsActivity.this, mDelegation.getBottomSheet().getEditText()); + } else { + LoginActivity.show(CommentsActivity.this); + } + } + }); + + mDelegation.getBottomSheet().getEditText().setOnKeyArrivedListener(new OnKeyArrivedListenerAdapterV2(this)); + + mDelegation.getBottomSheet().getEditText().setOnKeyListener(new View.OnKeyListener() { + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + EditText view = (EditText) v; + if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) { + Button mBtnView = mDelegation.getBottomSheet().getBtnCommit(); + Object o = mBtnView.getTag(); + if (o == null) return false; + if (!TextUtils.isEmpty(view.getText().toString())) { + mInputDoubleEmpty = false; + return false; + } + if (TextUtils.isEmpty(view.getText().toString()) && !mInputDoubleEmpty) { + mInputDoubleEmpty = true; + return false; + } + mBtnView.setTag(null); + view.setHint(mSourceId); + return true; + } + return false; + } + }); + + mDelegation.getBottomSheet().setCommitListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + sendComment(mType, mId, (Comment) v.getTag(), mDelegation.getBottomSheet().getCommentText()); + } + }); + + mRefreshLayout.setColorSchemeResources( + R.color.swiperefresh_color1, R.color.swiperefresh_color2, + R.color.swiperefresh_color3, R.color.swiperefresh_color4); + LinearLayoutManager manager = new LinearLayoutManager(this); + mLayComments.setLayoutManager(manager); + + mCommentAdapter = new CommentAdapter(this, getImageLoader(), BaseRecyclerAdapter.ONLY_FOOTER); + mCommentAdapter.setSourceId(mId); + mCommentAdapter.setCommentType(mType); + mCommentAdapter.setDelegation(mDelegation); + //mCommentAdapter.setOnItemLongClickListener(this); + mCommentAdapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + mComment = mCommentAdapter.getItem(position); + if (mType == OSChinaApi.COMMENT_QUESTION) { + QuesAnswerDetailActivity.show(CommentsActivity.this, mComment, mId, mType); + } else { + mShareCommentDialog.show(); + } + } + }); + mLayComments.setAdapter(mCommentAdapter); + } + + @Override + protected void initData() { + super.initData(); + + mRefreshLayout.setSuperRefreshLayoutListener(new RecyclerRefreshLayout.SuperRefreshLayoutListener() { + @Override + public void onRefreshing() { + getData(true, null); + } + + @Override + public void onLoadMore() { + String token = null; + if (mPageBean != null) + token = mPageBean.getNextPageToken(); + getData(false, token); + } + + @Override + public void onScrollToBottom() { + + } + }); + + mRefreshLayout.post(new Runnable() { + @Override + public void run() { + //第一次请求初始化数据 + getData(true, null); + + } + }); + + } + + Type getCommentType() { + return new TypeToken>>() { + }.getType(); + } + + /** + * 检查当前数据,并检查网络状况 + * + * @return 返回当前登录用户, 未登录或者未通过检查返回0 + */ + private long requestCheck() { + if (mId == 0) { + AppContext.showToast(getResources().getString(R.string.state_loading_error)); + return 0; + } + if (!TDevice.hasInternet()) { + AppContext.showToastShort(R.string.tip_no_internet); + return 0; + } + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(this); + return 0; + } + // 返回当前登录用户ID + return AccountHelper.getUserId(); + } + + + /** + * sync the tweet + */ + private void handleSyncTweet() { + if (mDelegation.getBottomSheet().isSyncToTweet()) { + TweetPublishService.startActionPublish(CommentsActivity.this, + mDelegation.getBottomSheet().getCommentText(), null, + About.buildShare(mId, mType)); + } + } + + /** + * handle send comment + */ + private void sendComment(int type, long id, Comment comment, String content) { + if (requestCheck() == 0) + return; + + if (TextUtils.isEmpty(content)) { + AppContext.showToastShort(R.string.tip_comment_content_empty); + return; + } + + long uid = comment == null ? 0 : comment.getAuthor().getId(); + long cid = comment == null ? 0 : comment.getId(); + + switch (type) { + case OSChinaApi.COMMENT_QUESTION: + OSChinaApi.pubQuestionComment(id, cid, uid, content, mHandler); + break; + case OSChinaApi.COMMENT_BLOG: + OSChinaApi.pubBlogComment(id, cid, uid, content, mHandler); + break; + case OSChinaApi.COMMENT_TRANSLATION: + OSChinaApi.pubTranslateComment(id, cid, uid, content, mHandler); + break; + case OSChinaApi.COMMENT_EVENT: + if (comment != null) { + content = "回复@" + comment.getAuthor().getName() + " : " + content; + } + OSChinaApi.pubEventComment(id, 0, 0, content, mHandler); + break; + case OSChinaApi.COMMENT_NEWS: + OSChinaApi.pubNewsComment(id, cid, uid, content, mHandler); + break; + case OSChinaApi.COMMENT_SOFT: + OSChinaApi.pubSoftComment(id, 0, 0, content, mHandler); + break; + default: + break; + } + + } + + private void getData(final boolean clearData, String token) { + OSChinaApi.getComments(mId, mType, "refer,reply", mOrder, token, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (isDestroy()) + return; + mCommentAdapter.setState(BaseRecyclerAdapter.STATE_LOAD_ERROR, true); + } + + @Override + public void onFinish() { + super.onFinish(); + if (isDestroy()) + return; + mRefreshLayout.onComplete(); + } + + @SuppressLint("DefaultLocale") + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (isDestroy()) + return; + try { + + ResultBean> resultBean = AppOperator.createGson().fromJson(responseString, getCommentType()); + + if (resultBean.isSuccess()) { + mPageBean = resultBean.getResult(); + int titleHintId = R.string.comment_title_hint; + if (mType == OSChinaApi.COMMENT_EVENT || mType == OSChinaApi.COMMENT_QUESTION) { + titleHintId = R.string.answer_hint; + } + mToolBar.setTitle(String.format("%d%s%s", mPageBean.getTotalResults(), getString(R.string.item_hint), getString(titleHintId))); + handleData(mPageBean.getItems(), clearData); + } + + mCommentAdapter.setState( + mPageBean == null || mPageBean.getItems() == null || mPageBean.getItems().size() < 20 ? + BaseRecyclerAdapter.STATE_NO_MORE : BaseRecyclerAdapter.STATE_LOAD_MORE, true); + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + }); + } + + private void handleData(List comments, boolean clearData) { + if (clearData) + mCommentAdapter.clear(); + mCommentAdapter.addAll(comments); + } + + @Override + public void onLongClick(int position, long itemId) { + + final Comment comment = mCommentAdapter.getItem(position); + if (comment == null) return; + + String[] items; + // if (AccountHelper.getUserId() == (int) comment.getAuthor().getId()) { + // items = new String[]{getString(R.string.copy), getString(R.string.delete)}; + //} else { + items = new String[]{getString(R.string.copy)}; + // } + + DialogHelper.getSelectDialog(this, items, getString(R.string.cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + + switch (i) { + case 0: + TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(comment.getContent())); + break; + case 1: + // TODO: 2016/11/30 delete comment + break; + default: + break; + } + } + }).show(); + + } + + private static final int PERMISSION_ID = 0x0001; + + @SuppressWarnings("unused") + @AfterPermissionGranted(PERMISSION_ID) + public void saveToFileByPermission() { + String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + if (EasyPermissions.hasPermissions(this, permissions)) { + mShareView.share(); + } else { + EasyPermissions.requestPermissions(this, "请授予文件读写权限", PERMISSION_ID, permissions); + } + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + DialogHelper.getConfirmDialog(this, "", "没有权限, 你需要去设置中开启读取手机存储权限.", "去设置", "取消", false, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + //finish(); + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //finish(); + } + }).show(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + @Override + protected void onResume() { + super.onResume(); + if (mShareView != null) + mShareView.dismiss(); + } + + @Override + public void finish() { + if (isAddCommented) { + setResult(RESULT_OK, new Intent()); + } + super.finish(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/comment/CommentsUtil.java b/app/src/main/java/net/oschina/app/improve/comment/CommentsUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..e65af310733acbf5781184fa05c1e69c6b1ea351 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/comment/CommentsUtil.java @@ -0,0 +1,172 @@ +package net.oschina.app.improve.comment; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.ShapeDrawable; +import android.text.Html; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ImageSpan; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.emoji.InputHelper; +import net.oschina.app.improve.bean.comment.Refer; +import net.oschina.app.improve.bean.comment.Reply; +import net.oschina.app.improve.utils.parser.StringParser; +import net.oschina.app.util.TDevice; +import net.oschina.app.widget.MyLinkMovementMethod; +import net.oschina.app.widget.MyURLSpan; +import net.oschina.app.widget.TweetTextView; +import net.oschina.common.widget.drawable.shape.BorderShape; + +/** + * Created by JuQiu + * on 16/6/21. + */ + +public final class CommentsUtil { + + @SuppressWarnings("deprecation") + public static View getReferLayout(LayoutInflater inflater, Refer[] refer, int count) { + Context context = inflater.getContext(); + @SuppressLint("InflateParams") + ViewGroup lay = (ViewGroup) inflater.inflate(R.layout.lay_comment_item_refer, null, false); + ShapeDrawable drawable = new ShapeDrawable(new BorderShape(new RectF(TDevice.dipToPx(context.getResources(), 1), 0, 0, 0))); + drawable.getPaint().setColor(0xffd7d6da); + lay.findViewById(R.id.lay_blog_detail_comment_refer).setBackgroundDrawable(drawable); + + TextView textView = ((TextView) lay.findViewById(R.id.tv_blog_detail_comment_refer)); + drawable = new ShapeDrawable(new BorderShape(new RectF(0, 0, 0, 1))); + drawable.getPaint().setColor(0xffd7d6da); + textView.setBackgroundDrawable(drawable); + formatHtml(context.getResources(), textView, refer[refer.length - 1 - count].getAuthor() + ":
    " + refer[refer.length - 1 - count].getContent()); + if (count < (refer.length < 5 ? refer.length - 1 : 4)) { + count++; + View view = getReferLayout(inflater, refer, count); + lay.addView(view, lay.indexOfChild(textView)); + } + + return lay; + } + + @SuppressWarnings("deprecation") + static View getReplyLayout(LayoutInflater inflater, Reply[] replies, int count) { + Context context = inflater.getContext(); + @SuppressLint("InflateParams") + ViewGroup lay = (ViewGroup) inflater.inflate(R.layout.lay_comment_item_refer, null, false); + ShapeDrawable drawable = new ShapeDrawable(new BorderShape(new RectF(TDevice.dipToPx(context.getResources(), 1), 0, 0, 0))); + drawable.getPaint().setColor(0xffd7d6da); + lay.findViewById(R.id.lay_blog_detail_comment_refer).setBackgroundDrawable(drawable); + + TextView textView = ((TextView) lay.findViewById(R.id.tv_blog_detail_comment_refer)); + drawable = new ShapeDrawable(new BorderShape(new RectF(0, 0, 0, 1))); + drawable.getPaint().setColor(0xffd7d6da); + textView.setBackgroundDrawable(drawable); + + formatHtml(context.getResources(), textView, replies[replies.length - 1 - count].getAuthor().getName() + ":
    " + replies[replies.length - 1 - count].getContent()); + + if (count < (replies.length < 5 ? replies.length - 1 : 4)) { + count++; + View view = getReplyLayout(inflater, replies, count); + lay.addView(view, lay.indexOfChild(textView)); + } + + return lay; + } + + @SuppressWarnings("deprecation") + public static void formatHtml(Resources resources, TextView textView, String str) { + if (str == null) + return; + str = str.trim(); + + textView.setMovementMethod(MyLinkMovementMethod.a()); + textView.setFocusable(false); + textView.setClickable(false); + textView.setLongClickable(false); + + if (textView instanceof TweetTextView) { + ((TweetTextView) textView).setDispatchToParent(true); + } + + str = TweetTextView.modifyPath(str); + Spanned span = Html.fromHtml(str); + span = InputHelper.displayEmoji(resources, span.toString()); + textView.setText(span); + MyURLSpan.parseLinkText(textView, span); + } + + @SuppressWarnings({"unused", "deprecation"}) + public static void formatHtml(Resources resources, TextView textView, String str, boolean isShare,boolean isRef) { + if (str == null) + return; + str = str.trim(); + + textView.setMovementMethod(MyLinkMovementMethod.a()); + textView.setFocusable(false); + textView.setLongClickable(false); + + if (textView instanceof TweetTextView) { + ((TweetTextView) textView).setDispatchToParent(true); + } + + Spanned span = StringParser.getInstance().parse(textView.getContext(), str); + + if (isShare) { + if(isRef){ + + span = InputHelper.displayEmoji(resources, span.toString()); + SpannableStringBuilder sb = new SpannableStringBuilder(); + sb.append(span); + + textView.setText(sb); + float size = 26.0f; + int count = sb.length() / 32; + if (count > 3) { + size = size / count * 3; + } + textView.setTextSize(Math.max(16.0f, size)); + //MyURLSpan.parseLinkText(textView, span); + }else { + Drawable drawableLeft = resources.getDrawable(R.mipmap.ic_quote_left); + drawableLeft.setBounds(0, 0, drawableLeft.getIntrinsicWidth(), drawableLeft.getIntrinsicHeight()); + ImageSpan imageSpanLeft = new ImageSpan(drawableLeft, ImageSpan.ALIGN_BASELINE); + + Drawable drawableRight = resources.getDrawable(R.mipmap.ic_quote_right); + drawableRight.setBounds(0, 0, drawableRight.getIntrinsicWidth(), drawableRight.getIntrinsicHeight()); + ImageSpan imageSpanRight = new ImageSpan(drawableRight, ImageSpan.ALIGN_BASELINE); + + span = InputHelper.displayEmoji(resources, span.toString()); + SpannableStringBuilder sb = new SpannableStringBuilder(); + sb.append("[icon] "); + sb.append(span); + sb.append(" [icon]"); + sb.setSpan(imageSpanLeft, 0, 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + sb.setSpan(imageSpanRight, sb.length() - 6, sb.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + + textView.setText(sb); + float size = 26.0f; + int count = sb.length() / 32; + if (count > 3) { + size = size / count * 3; + } + textView.setTextSize(Math.max(16.0f, size)); + //MyURLSpan.parseLinkText(textView, span); + } + } else { + span = InputHelper.displayEmoji(resources, span.toString()); + textView.setText(span); + textView.setTextSize(14.0f); + textView.setText(span); + //MyURLSpan.parseLinkText(textView, span); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/comment/OnCommentClickListener.java b/app/src/main/java/net/oschina/app/improve/comment/OnCommentClickListener.java new file mode 100644 index 0000000000000000000000000000000000000000..a46a473ad910779db5b3322a5c351f97fb0a4eca --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/comment/OnCommentClickListener.java @@ -0,0 +1,14 @@ +package net.oschina.app.improve.comment; + +import android.view.View; + +import net.oschina.app.improve.bean.comment.Comment; + +/** + * Created by JuQiu + * on 16/6/21. + */ + +public interface OnCommentClickListener { + void onClick(View view, Comment comment); +} diff --git a/app/src/main/java/net/oschina/app/improve/comment/QuesAnswerDetailActivity.java b/app/src/main/java/net/oschina/app/improve/comment/QuesAnswerDetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..3b1ef41fbf9e48f94fce040ba49ad9f93d14cb84 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/comment/QuesAnswerDetailActivity.java @@ -0,0 +1,537 @@ +package net.oschina.app.improve.comment; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.design.widget.CoordinatorLayout; +import android.support.v4.widget.NestedScrollView; +import android.support.v7.app.ActionBar; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.comment.Reply; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.tweet.adapter.TweetCommentAdapter; +import net.oschina.app.improve.tweet.fragments.TweetPublishFragment; +import net.oschina.app.improve.tweet.service.TweetPublishService; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.OWebView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.improve.widget.adapter.OnKeyArrivedListenerAdapterV2; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import butterknife.Bind; +import butterknife.ButterKnife; +import butterknife.OnClick; +import cz.msebera.android.httpclient.Header; + +/** + * Created by thanatos + * on 16/6/16. + * change by fie + * on 16/11/17 + * desc:问答,活动的评论详情(相当于帖子),可以对评论进行顶踩操作 + */ +public class QuesAnswerDetailActivity extends BackActivity { + + public static final String BUNDLE_KEY = "BUNDLE_KEY"; + public static final String BUNDLE_ARTICLE_KEY = "BUNDLE_ARTICLE_KEY"; + public static final String BUNDLE_TYPE = "bundle_comment_type"; + + @Bind(R.id.iv_portrait) + PortraitView ivPortrait; + @Bind(R.id.identityView) + IdentityView identityView; + @Bind(R.id.tv_nick) + TextView tvNick; + @Bind(R.id.tv_time) + TextView tvTime; + @Bind(R.id.iv_vote_up) + ImageView ivVoteUp; + @Bind(R.id.iv_vote_down) + ImageView ivVoteDown; + @Bind(R.id.tv_up_count) + TextView tvVoteCount; + @Bind(R.id.webview) + OWebView mWebView; + @Bind(R.id.tv_comment_count) + TextView tvCmnCount; + @Bind(R.id.layout_container) + LinearLayout mLayoutContainer; + @Bind(R.id.layout_coordinator) + CoordinatorLayout mCoordinatorLayout; + @Bind(R.id.layout_scroll) + NestedScrollView mScrollView; + + private long sid; + private Comment comment; + private int mType; + + private Dialog mVoteDialog; + private Reply reply; + private View mVoteDialogView; + private List replies = new ArrayList<>(); + + private CommentBar mDelegation; + private TextHttpResponseHandler onSendCommentHandler; + private View.OnClickListener onReplyButtonClickListener; + + /** + * @param context context + * @param comment comment + * @param sid 文章id + */ + public static void show(Context context, Comment comment, long sid, int type) { + Intent intent = new Intent(context, QuesAnswerDetailActivity.class); + intent.putExtra(BUNDLE_KEY, comment); + intent.putExtra(BUNDLE_ARTICLE_KEY, sid); + intent.putExtra(BUNDLE_TYPE, type); + context.startActivity(intent); + } + + @Override + protected boolean initBundle(Bundle bundle) { + comment = (Comment) getIntent().getSerializableExtra(BUNDLE_KEY); + sid = getIntent().getLongExtra(BUNDLE_ARTICLE_KEY, 0); + mType = getIntent().getIntExtra(BUNDLE_TYPE, OSChinaApi.COMMENT_QUESTION); + return !(comment == null || comment.getId() <= 0) && super.initBundle(bundle); + } + + @Override + protected int getContentView() { + return R.layout.activity_post_answer_detail; + } + + @Override + protected void initWindow() { + super.initWindow(); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(R.string.back_hint); + } + } + + @SuppressLint("SetTextI18n") + @SuppressWarnings("deprecation") + protected void initWidget() { + setStatusBarDarkMode(); + setDarkToolBar(); + // portrait + ivPortrait.setup(comment.getAuthor()); + identityView.setup(comment.getAuthor().getIdentity()); + // nick + tvNick.setText(comment.getAuthor().getName()); + + // publish time + if (!TextUtils.isEmpty(comment.getPubDate())) + tvTime.setText(StringUtils.formatSomeAgo(comment.getPubDate())); + + // vote state + switch (comment.getVoteState()) { + case Comment.VOTE_STATE_UP: + ivVoteUp.setSelected(true); + break; + case Comment.VOTE_STATE_DOWN: + ivVoteDown.setSelected(true); + } + + // vote count + tvVoteCount.setText(String.valueOf(comment.getVote())); + + tvCmnCount.setText("评论 (" + (comment.getReply() == null ? 0 : comment.getReply().length) + ")"); + + mDelegation = CommentBar.delegation(this, mCoordinatorLayout); + + mDelegation.setCommentHint("我要回答"); + mDelegation.getBottomSheet().getEditText().setHint("我要回答"); + mDelegation.getBottomSheet().getEditText().setText(""); + + mDelegation.hideFav(); + mDelegation.hideCommentCount(); + + mDelegation.getBottomSheet().getEditText().setOnKeyArrivedListener(new OnKeyArrivedListenerAdapterV2(this)); + + mDelegation.getBottomSheet().setMentionListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (AccountHelper.isLogin()) { + Intent intent = new Intent(QuesAnswerDetailActivity.this, UserSelectFriendsActivity.class); + startActivityForResult(intent, TweetPublishFragment.REQUEST_CODE_SELECT_FRIENDS); + } else + LoginActivity.show(QuesAnswerDetailActivity.this); + } + }); + + mDelegation.getBottomSheet().setCommitListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String content = mDelegation.getBottomSheet().getCommentText(); + if (TextUtils.isEmpty(content.replaceAll("[ \\s\\n]+", ""))) { + Toast.makeText(QuesAnswerDetailActivity.this, "请输入文字", Toast.LENGTH_SHORT).show(); + return; + } + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(QuesAnswerDetailActivity.this); + return; + } + if (comment == null || comment.getAuthor() == null) + return; + OSChinaApi.publishComment(sid, -1, comment.getId(), comment.getAuthor().getId(), 2, content, onSendCommentHandler); + } + }); + + mDelegation.getBottomSheet().getEditText().setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) { + if (reply == null) return false; + reply = null; + mDelegation.setCommentHint("我要回答"); + mDelegation.getBottomSheet().getEditText().setHint("我要回答"); + } + return false; + } + }); + + Reply[] reply = comment.getReply(); + if (reply != null) { + mLayoutContainer.removeAllViews(); + replies.clear(); + Collections.addAll(replies, comment.getReply()); + Collections.reverse(replies); // 反转集合, 最新的评论在集合后面 + for (int i = 0; i < replies.size(); i++) { + appendComment(i, replies.get(i)); + } + } + + fillWebView(); + } + + private void fillWebView() { + if (TextUtils.isEmpty(comment.getContent())) return; + if (mWebView != null) + mWebView.loadDetailDataAsync(comment.getContent(), new Runnable() { + @Override + public void run() { + + } + }); + } + + @SuppressWarnings("deprecation") + private void appendComment(int i, Reply reply) { + View view = LayoutInflater.from(this).inflate(R.layout.list_item_tweet_comment, mLayoutContainer, false); + TweetCommentAdapter.TweetCommentHolderView holder = new TweetCommentAdapter.TweetCommentHolderView(view); + Author author = reply.getAuthor(); + if (author != null) { + holder.tvName.setText(author.getName()); + holder.ivPortrait.setup(author); + holder.identityView.setup(author); + } + + holder.tvTime.setText(String.format("%s楼 %s", i + 1, StringUtils.formatSomeAgo(reply.getPubDate()))); + CommentsUtil.formatHtml(getResources(), holder.tvContent, reply.getContent()); + holder.btnReply.setTag(reply); + holder.btnReply.setOnClickListener(getOnReplyButtonClickListener()); + mLayoutContainer.addView(view, 0); + } + + private View.OnClickListener getOnReplyButtonClickListener() { + if (onReplyButtonClickListener == null) { + onReplyButtonClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + Reply reply = (Reply) v.getTag(); + Author author = reply.getAuthor(); + if (author != null) + mDelegation.setCommentHint("回复 @" + author.getName() + " : "); + + QuesAnswerDetailActivity.this.reply = reply; + } + }; + } + return onReplyButtonClickListener; + } + + protected void initData() { + onSendCommentHandler = new TextHttpResponseHandler() { + + @Override + public void onStart() { + super.onStart(); + if (mDelegation == null || isDestroy()) return; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(false); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (mDelegation == null || isDestroy()) return; + AppContext.showToastShort("评论失败"); + } + + @SuppressLint("SetTextI18n") + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (isDestroy()) return; + try { + ResultBean result = AppOperator.createGson().fromJson( + responseString, + new TypeToken>() { + }.getType() + ); + if (result.isSuccess()) { + replies.add(result.getResult()); + tvCmnCount.setText("评论 (" + replies.size() + ")"); + reply = null; + mDelegation.setCommentHint("我要回答"); + mDelegation.getBottomSheet().getEditText().setHint("我要回答"); + mDelegation.getBottomSheet().getEditText().setText(""); + mDelegation.getBottomSheet().getBtnCommit().setTag(null); + appendComment(replies.size() - 1, result.getResult()); + boolean syncToTweet = mDelegation.getBottomSheet().isSyncToTweet(); + if (syncToTweet) { + TweetPublishService.startActionPublish(QuesAnswerDetailActivity.this, + mDelegation.getBottomSheet().getCommentText(), null, + About.buildShare(sid, mType)); + } + } else { + AppContext.showToastShort(result.getMessage()); + } + mDelegation.getBottomSheet().dismiss(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onFinish() { + super.onFinish(); + if(isDestroy()) + return; + if (mDelegation == null || isDestroy()) return; + mDelegation.setCommitButtonEnable(true); + mDelegation.getBottomSheet().dismiss(); + } + }; + + if (comment == null || comment.getAuthor() == null) + return; + OSChinaApi.getCommentDetail(comment.getId(), comment.getAuthor().getId(), mType, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String respStr, Throwable throwable) { + if (isDestroy()) return; + AppContext.showToastShort("请求失败"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String respStr) { + if (isDestroy()) return; + try { + ResultBean result = AppOperator.createGson().fromJson(respStr, + new TypeToken>() { + }.getType()); + if (result.isSuccess()) { + Comment cmn = result.getResult(); + if (cmn != null && cmn.getId() > 0) { + comment = cmn; + initWidget(); + return; + } + } + AppContext.showToastShort("请求失败"); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @SuppressLint("InflateParams") + private View getVoteDialogView() { + if (mVoteDialogView == null) { + mVoteDialogView = LayoutInflater.from(this).inflate(R.layout.dialog_question_comment_detail_vote, null, false); + final VoteViewHolder holder = new VoteViewHolder(mVoteDialogView); + View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(final View v) { + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(QuesAnswerDetailActivity.this); + return; + } + final int opt = (int) v.getTag(); + switch (opt) { + case Comment.VOTE_STATE_UP: + if (ivVoteDown.isSelected()) { + AppContext.showToastShort("你已经踩过了"); + return; + } + holder.mVoteUp.setVisibility(View.GONE); + holder.mProgressBar.setVisibility(View.VISIBLE); + break; + case Comment.VOTE_STATE_DOWN: + if (ivVoteUp.isSelected()) { + AppContext.showToastShort("你已经顶过了"); + return; + } + holder.mVoteDown.setVisibility(View.GONE); + holder.mProgressBar.setVisibility(View.VISIBLE); + break; + } + OSChinaApi.questionVote(sid, comment.getId(), opt, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (isDestroy()) { + return; + } + AppContext.showToastShort("操作失败"); + if (mVoteDialog != null && mVoteDialog.isShowing()) { + switch (opt) { + case Comment.VOTE_STATE_UP: + holder.mVoteUp.setVisibility(View.VISIBLE); + holder.mProgressBar.setVisibility(View.GONE); + break; + case Comment.VOTE_STATE_DOWN: + holder.mVoteDown.setVisibility(View.VISIBLE); + holder.mProgressBar.setVisibility(View.GONE); + break; + } + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean result = AppOperator.createGson().fromJson( + responseString, new TypeToken>() { + }.getType()); + if (result.isSuccess()) { + comment.setVoteState(result.getResult().getVoteState()); + comment.setVote((int) result.getResult().getVote()); + tvVoteCount.setText(String.valueOf(result.getResult().getVote())); + v.setSelected(!v.isSelected()); + switch (opt) { + case Comment.VOTE_STATE_UP: + ivVoteUp.setSelected(!ivVoteUp.isSelected()); + break; + case Comment.VOTE_STATE_DOWN: + ivVoteDown.setSelected(!ivVoteDown.isSelected()); + break; + } + AppContext.showToastShort("操作成功"); + } else { + AppContext.showToastShort(TextUtils.isEmpty(result.getMessage()) + ? "操作失败" : result.getMessage()); + } + if (mVoteDialog != null) mVoteDialog.dismiss(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + }; + holder.mVoteUp.setTag(Comment.VOTE_STATE_UP); + holder.mVoteDown.setTag(Comment.VOTE_STATE_DOWN); + holder.mVoteUp.setOnClickListener(listener); + holder.mVoteDown.setOnClickListener(listener); + mVoteDialogView.setTag(holder); + } else { + ViewGroup view = (ViewGroup) mVoteDialogView.getParent(); + view.removeView(mVoteDialogView); + } + VoteViewHolder holder = (VoteViewHolder) mVoteDialogView.getTag(); + holder.mVoteDown.setVisibility(View.VISIBLE); + holder.mVoteUp.setVisibility(View.VISIBLE); + holder.mProgressBar.setVisibility(View.GONE); + switch (comment.getVoteState()) { + default: + holder.mVoteUp.setSelected(false); + holder.mVoteDown.setSelected(false); + holder.mVoteUp.setText("顶"); + holder.mVoteDown.setText("踩"); + break; + case Comment.VOTE_STATE_UP: + holder.mVoteUp.setSelected(true); + holder.mVoteDown.setSelected(false); + holder.mVoteUp.setText("已顶"); + holder.mVoteDown.setText("踩"); + break; + case Comment.VOTE_STATE_DOWN: + holder.mVoteUp.setSelected(false); + holder.mVoteDown.setSelected(true); + holder.mVoteUp.setText("顶"); + holder.mVoteDown.setText("已踩"); + break; + } + return mVoteDialogView; + } + + @SuppressWarnings("ConstantConditions") + @OnClick(R.id.layout_vote) + void onClickVote() { + mVoteDialog = DialogHelper.getDialog(this) + .setView(getVoteDialogView()) + .create(); + mVoteDialog.show(); + WindowManager.LayoutParams params = mVoteDialog.getWindow().getAttributes(); + params.width = (int) TDevice.dp2px(260f); + mVoteDialog.getWindow().setAttributes(params); + } + + static class VoteViewHolder { + @Bind(R.id.btn_vote_up) + TextView mVoteUp; + @Bind(R.id.btn_vote_down) + TextView mVoteDown; + @Bind(R.id.progress) + ProgressBar mProgressBar; + + VoteViewHolder(View view) { + ButterKnife.bind(this, view); + } + } + + @Override + protected void onDestroy() { + final OWebView webView = mWebView; + if (webView != null) { + mWebView = null; + webView.destroy(); + } + super.onDestroy(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/comment/adapter/CommentAdapter.java b/app/src/main/java/net/oschina/app/improve/comment/adapter/CommentAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..ffca7e974e2dc08916f9006d1cbfbf1027d40639 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/comment/adapter/CommentAdapter.java @@ -0,0 +1,315 @@ +package net.oschina.app.improve.comment.adapter; + +import android.annotation.SuppressLint; +import android.app.ProgressDialog; +import android.content.Context; +import android.support.annotation.StringRes; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.bumptech.glide.RequestManager; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.comment.Vote; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.comment.CommentReferView; +import net.oschina.app.improve.comment.CommentsUtil; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; +import net.oschina.app.widget.TweetTextView; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import butterknife.ButterKnife; +import cz.msebera.android.httpclient.Header; + + +/** + * Created by fei + * on 2016/11/21. + * desc: + */ +public class CommentAdapter extends BaseRecyclerAdapter { + + private static final int VIEW_TYPE_DATA_FOOTER = 2000; + private long mSourceId; + + private int mType; + private CommentBar delegation; + private RequestManager mRequestManager; + + public CommentAdapter(final Context context, RequestManager requestManager, int mode) { + super(context, mode); + this.mRequestManager = requestManager; + } + + @Override + protected CommentHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.lay_comment_refer_item, parent, false); + return new CommentHolder(view, delegation); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Comment item, int position) { + if (holder instanceof CommentHolder) { + ((CommentHolder) holder).addComment(mSourceId, mType, item, mRequestManager); + } + } + + @Override + public int getItemViewType(int position) { + int type = super.getItemViewType(position); + if (type == VIEW_TYPE_NORMAL && isRealDataFooter(position)) { + return VIEW_TYPE_DATA_FOOTER; + } + return type; + } + + public void setSourceId(long sourceId) { + this.mSourceId = sourceId; + } + + public void setCommentType(int Type) { + this.mType = Type; + } + + public void setDelegation(CommentBar delegation) { + this.delegation = delegation; + } + + private boolean isRealDataFooter(int position) { + return getIndex(position) == getCount() - 1; + } + + static class CommentHolder extends RecyclerView.ViewHolder { + + private ProgressDialog mDialog; + + @Bind(R.id.iv_avatar) + PortraitView mIvAvatar; + + @Bind(R.id.identityView) + IdentityView mIdentityView; + + @Bind(R.id.tv_name) + TextView mName; + @Bind(R.id.tv_pub_date) + TextView mPubDate; + @Bind(R.id.tv_vote_count) + TextView mVoteCount; + @Bind(R.id.btn_vote) + ImageView mVote; + + @Bind(R.id.iv_best_answer) + ImageView mImageBestAnswer; + + @Bind(R.id.lay_refer) + CommentReferView mCommentReferView; + + @Bind(R.id.tv_content) + TweetTextView mTweetTextView; + @Bind(R.id.line) + View mLine; + + private CommentBar commentBar; + + CommentHolder(View itemView, CommentBar commentBar) { + super(itemView); + ButterKnife.bind(this, itemView); + this.commentBar = commentBar; + } + + /** + * add comment + * + * @param comment comment + */ + @SuppressLint("DefaultLocale") + void addComment(final long sourceId, final int commentType, final Comment comment, RequestManager requestManager) { + mIdentityView.setup(comment.getAuthor()); + mIvAvatar.setup(comment.getAuthor()); + mIvAvatar.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + OtherUserHomeActivity.show(mIvAvatar.getContext(), comment.getAuthor().getId()); + } + }); + Author author = comment.getAuthor(); + String name; + if (author == null || TextUtils.isEmpty(name = author.getName())) + name = mName.getResources().getString(R.string.martian_hint); + mName.setText(name); + mPubDate.setText(String.format("%s", StringUtils.formatSomeAgo(comment.getPubDate()))); + + + if (commentType == OSChinaApi.COMMENT_QUESTION || commentType == OSChinaApi.COMMENT_EVENT + || commentType == OSChinaApi.COMMENT_BLOG || commentType == OSChinaApi.COMMENT_TRANSLATION + || commentType == OSChinaApi.COMMENT_SOFT) { + mVoteCount.setVisibility(View.GONE); + mVote.setVisibility(View.GONE); + if (comment.isBest()) { + mImageBestAnswer.setEnabled(false); + mImageBestAnswer.setImageResource(R.mipmap.label_best_answer); + mImageBestAnswer.setVisibility(View.VISIBLE); + } + } else { + mVoteCount.setText(String.valueOf(comment.getVote())); + mVoteCount.setVisibility(View.VISIBLE); + mVote.setVisibility(View.VISIBLE); + if (comment.getVoteState() == 1) { + mVote.setImageResource(R.mipmap.ic_thumbup_actived); + mVote.setTag(true); + } else if (comment.getVoteState() == 0) { + mVote.setImageResource(R.mipmap.ic_thumb_normal); + mVote.setTag(null); + } + mVote.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + handVote(); + } + + private void handVote() { + if (!AccountHelper.isLogin()) { + LoginActivity.show(mVote.getContext()); + return; + } + if (!TDevice.hasInternet()) { + AppContext.showToast(mVote.getResources().getString(R.string.state_network_error), Toast.LENGTH_SHORT); + return; + } + OSChinaApi.voteComment(commentType, comment.getId(), comment.getAuthor().getId(), mVote.getTag() != null ? 0 : 1, new TextHttpResponseHandler() { + + @Override + public void onStart() { + super.onStart(); + showWaitDialog(R.string.progress_submit); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + requestFailureHint(throwable); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, getVoteType()); + if (resultBean.isSuccess()) { + Vote vote = resultBean.getResult(); + if (vote != null) { + if (vote.getVoteState() == 1) { + comment.setVoteState(1); + mVote.setTag(true); + mVote.setImageResource(R.mipmap.ic_thumbup_actived); + } else if (vote.getVoteState() == 0) { + comment.setVoteState(0); + mVote.setTag(null); + mVote.setImageResource(R.mipmap.ic_thumb_normal); + } + long voteVoteCount = vote.getVote(); + comment.setVote(voteVoteCount); + mVoteCount.setText(String.valueOf(voteVoteCount)); + } + } else { + AppContext.showToast(resultBean.getMessage(), Toast.LENGTH_SHORT); + } + } + + @Override + public void onFinish() { + super.onFinish(); + hideWaitDialog(); + } + }); + } + }); + } + + mCommentReferView.addComment(comment); + + CommentsUtil.formatHtml(mTweetTextView.getResources(), mTweetTextView, comment.getContent()); + } + + /** + * show WaitDialog + * + * @return progressDialog + */ + private ProgressDialog showWaitDialog(@StringRes int messageId) { + + if (mDialog == null) { + if (messageId <= 0) { + mDialog = DialogHelper.getProgressDialog(mVote.getContext(), true); + } else { + String message = mVote.getContext().getResources().getString(messageId); + mDialog = DialogHelper.getProgressDialog(mVote.getContext(), message, true); + } + } + mDialog.show(); + + return mDialog; + } + + /** + * hide waitDialog + */ + private void hideWaitDialog() { + ProgressDialog dialog = mDialog; + if (dialog != null) { + mDialog = null; + try { + dialog.cancel(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + /** + * request network error + * + * @param throwable throwable + */ + private void requestFailureHint(Throwable throwable) { + AppContext.showToast(R.string.request_error_hint); + if (throwable != null) { + throwable.printStackTrace(); + } + } + + /** + * @return TypeToken + */ + Type getVoteType() { + return new TypeToken>() { + }.getType(); + } + + } + + +} diff --git a/app/src/main/java/net/oschina/app/improve/comment/adapter/CommentItemAdapter.java b/app/src/main/java/net/oschina/app/improve/comment/adapter/CommentItemAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..ad72a5b6bff1a9a9e74c98d3031e78b17e38ff76 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/comment/adapter/CommentItemAdapter.java @@ -0,0 +1,74 @@ +package net.oschina.app.improve.comment.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; + +/** + * 评论项目 复制、评论、分享 + * Created by haibin on 2017/4/24. + */ + +public class CommentItemAdapter extends BaseRecyclerAdapter { + public CommentItemAdapter(Context context) { + super(context, NEITHER); + addItem(new CommentItem("复制", R.mipmap.ic_copy)); + addItem(new CommentItem("评论", R.mipmap.ic_comment_40_pressed)); + addItem(new CommentItem("分享", R.mipmap.ic_share_black_pressed)); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new CommentViewHolder(mInflater.inflate(R.layout.item_list_comment_item, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, CommentItem item, int position) { + CommentViewHolder h = (CommentViewHolder) holder; + h.mTextItem.setText(item.getItem()); + h.mImageItem.setImageResource(item.getIcon()); + } + + private static class CommentViewHolder extends RecyclerView.ViewHolder { + private ImageView mImageItem; + private TextView mTextItem; + + CommentViewHolder(View itemView) { + super(itemView); + mTextItem = (TextView) itemView.findViewById(R.id.tv_comment); + mImageItem = (ImageView) itemView.findViewById(R.id.iv_comment); + } + } + + static class CommentItem { + private String item; + private int icon; + + CommentItem(String item, int icon) { + this.item = item; + this.icon = icon; + } + + public String getItem() { + return item; + } + + public void setItem(String item) { + this.item = item; + } + + public int getIcon() { + return icon; + } + + public void setIcon(int icon) { + this.icon = icon; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/SubActivity.java b/app/src/main/java/net/oschina/app/improve/detail/SubActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..01ef519998512875948fa8c1483968f746e2412d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/SubActivity.java @@ -0,0 +1,41 @@ +package net.oschina.app.improve.detail; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.SubTab; +import net.oschina.app.improve.main.subscription.SubFragment; + +/** + * 活动界面 + * Created by huanghaibin on 2017/10/28. + */ + +public class SubActivity extends BackActivity { + + public static void show(Context context, SubTab bean) { + if (bean == null) + return; + Intent intent = new Intent(context, SubActivity.class); + intent.putExtra("sub_tab", bean); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_setting; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + SubTab tab = (SubTab) getIntent().getSerializableExtra("sub_tab"); + mToolBar.setTitle(tab.getName()); + setTitle(tab.getName()); + addFragment(R.id.fl_content, SubFragment.newInstance(tab)); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyActivity.java b/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..0fd272bf727138948c249e4d496cf98e43e8b91d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyActivity.java @@ -0,0 +1,157 @@ +package net.oschina.app.improve.detail.apply; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.SearchView; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.View; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.util.TDevice; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * Created by haibin + * on 2016/12/27. + */ + +public class ApplyActivity extends BackActivity implements + View.OnClickListener, ApplyContract.EmptyView { + @Bind(R.id.searchView) + SearchView mSearchView; + + @Bind(R.id.emptyLayout) + EmptyLayout mEmptyLayout; + + @Bind(R.id.ll_search) + LinearLayout mLinearSearch; + + @Bind(R.id.fl_tool) + FrameLayout mFrameTool; + @Bind(R.id.search_src_text) + EditText mViewSearchEditor; + private ApplyPresenter mPresenter; + private String mSearchText; + + public static void show(Context context, long sourceId) { + Intent intent = new Intent(context, ApplyActivity.class); + intent.putExtra("sourceId", sourceId); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_apply; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + long sourceId = getIntent().getLongExtra("sourceId", 0); + if (sourceId == 0) { + SimplexToast.show(this, "活动不存在"); + finish(); + return; + } + mViewSearchEditor.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + mSearchView.setOnCloseListener(new SearchView.OnCloseListener() { + @Override + public boolean onClose() { + // 阻止点击关闭按钮 collapse icon + return true; + } + }); + mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + mSearchView.clearFocus(); + return doSearch(query, false); + } + + @Override + public boolean onQueryTextChange(String newText) { + return doSearch(newText, true); + } + }); + ApplyFragment fragment = ApplyFragment.newInstance(); + mPresenter = new ApplyPresenter(fragment, this, sourceId); + addFragment(R.id.fl_content, fragment); + } + + @OnClick({R.id.ll_search, R.id.tv_cancel}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.ll_search: + mLinearSearch.setVisibility(View.GONE); + mSearchView.setVisibility(View.VISIBLE); + mSearchView.setFocusable(true); + mSearchView.setFocusableInTouchMode(true); + mSearchView.requestFocus(); + TDevice.openKeyboard(mSearchView); + break; + case R.id.tv_cancel: + mLinearSearch.setVisibility(View.VISIBLE); + mSearchView.setVisibility(View.GONE); + mViewSearchEditor.setText(""); + mPresenter.setFilter(""); + mSearchView.clearFocus(); + TDevice.hideSoftKeyboard(mSearchView); + break; + } + } + + @Override + public void showGetApplyUserSuccess() { + if (isDestroy()) + return; + mFrameTool.setVisibility(View.VISIBLE); + mEmptyLayout.setVisibility(View.GONE); + } + + @Override + public void showGetApplyUserError(String message) { + + } + + @Override + public void showSearchError(String message) { + + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + mSearchView.clearFocus(); + } + + private Runnable mSearchRunnable = new Runnable() { + @Override + public void run() { + if (TextUtils.isEmpty(mSearchText)) + return; + mPresenter.setFilter(mSearchText); + mPresenter.onRefreshing(); + } + }; + + private boolean doSearch(String query, boolean fromTextChange) { + mSearchText = query.trim(); + mPresenter.setFilter(mSearchText); + mLinearSearch.removeCallbacks(mSearchRunnable); + if (fromTextChange && !TDevice.isWifiOpen()) return false; + mLinearSearch.postDelayed(mSearchRunnable, fromTextChange ? 2000 : 0); + return true; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyAdapter.java b/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..bc639a48d8994c6445f02e03732fb43bd61177d8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyAdapter.java @@ -0,0 +1,82 @@ +package net.oschina.app.improve.detail.apply; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.bean.ApplyUser; +import net.oschina.app.improve.bean.simple.UserRelation; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; + +/** + * Created by haibin + * on 2016/12/27. + */ + +class ApplyAdapter extends BaseGeneralRecyclerAdapter { + private View.OnClickListener mRelationListener; + + ApplyAdapter(Callback callback, View.OnClickListener mRelationListener) { + super(callback, ONLY_FOOTER); + this.mRelationListener = mRelationListener; + } + + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ApplyViewHolder(mInflater.inflate(R.layout.item_list_apply, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, final ApplyUser item, int position) { + ApplyViewHolder h = (ApplyViewHolder) holder; + + if (item.getId() <= 0) + item.setName("匿名用户"); + h.mTextAuthor.setText(item.getName()); + h.mImageAuthor.setup(item); + h.mIdentityView.setup(item); + + ApplyUser.EventInfo info = item.getEventInfo(); + if (info != null) { + h.mTextCompany.setText(info.getCompany()); + h.mTextJob.setText(info.getJob()); + } else { + h.mTextCompany.setText(""); + h.mTextJob.setText(""); + } + h.mBtnRelate.setEnabled(item.getId() == 0 || AccountHelper.getUserId() != item.getId()); + h.mBtnRelate.setText(item.getRelation() >= UserRelation.RELATION_ONLY_HER ? "关注" : "已关注"); + h.mBtnRelate.setTag(position); + h.mBtnRelate.setOnClickListener(mRelationListener); + + h.mImageGender.setImageResource(item.getGender() == 1 ? R.mipmap.ic_male : R.mipmap.ic_female); + h.mImageGender.setVisibility(item.getGender() == 0 ? View.GONE : View.VISIBLE); + } + + private static class ApplyViewHolder extends RecyclerView.ViewHolder { + TextView mTextAuthor, mTextCompany, mTextJob; + PortraitView mImageAuthor; + IdentityView mIdentityView; + ImageView mImageGender; + Button mBtnRelate; + + ApplyViewHolder(View itemView) { + super(itemView); + mTextAuthor = (TextView) itemView.findViewById(R.id.tv_author); + mTextCompany = (TextView) itemView.findViewById(R.id.tv_company); + mTextJob = (TextView) itemView.findViewById(R.id.tv_job); + mImageAuthor = (PortraitView) itemView.findViewById(R.id.civ_author); + mIdentityView = (IdentityView) itemView.findViewById(R.id.identityView); + mBtnRelate = (Button) itemView.findViewById(R.id.btn_relation); + mImageGender = (ImageView) itemView.findViewById(R.id.iv_gender); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyContract.java b/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyContract.java new file mode 100644 index 0000000000000000000000000000000000000000..37096955116416a8d40719dc0d5f782c23e4232e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyContract.java @@ -0,0 +1,31 @@ +package net.oschina.app.improve.detail.apply; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.bean.ApplyUser; + +/** + * Created by haibin + * on 2016/12/27. + */ + + interface ApplyContract { + + interface EmptyView { + void showGetApplyUserSuccess(); + + void showGetApplyUserError(String message); + + void showSearchError(String message); + } + + interface View extends BaseListView { + void showAddRelationSuccess(int relation, int position); + + void showAddRelationError(); + } + + interface Presenter extends BaseListPresenter { + void addRelation(long authorId, int position); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyFragment.java b/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..260582b57eb6a3e8ca75442c75d1ddd4653c0d5f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyFragment.java @@ -0,0 +1,91 @@ +package net.oschina.app.improve.detail.apply; + +import android.app.ProgressDialog; +import android.view.View; + +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.ApplyUser; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.TDevice; + +/** + * Created by haibin + * on 2016/12/27. + */ + +public class ApplyFragment extends BaseRecyclerFragment + implements ApplyContract.View { + + private View.OnClickListener mRelationListener; + private ProgressDialog mProgressDialog; + + public static ApplyFragment newInstance() { + return new ApplyFragment(); + } + + @Override + protected void initWidget(View root) { + mRelationListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + int position = (int) v.getTag(); + ApplyUser applyUser = mAdapter.getItem(position); + assert applyUser != null; + if (applyUser.getId() == 0) { + SimplexToast.show(mContext, "不能关注匿名用户"); + return; + } + showDialog("正在添加关注..."); + mPresenter.addRelation(applyUser.getId(), position); + } + }; + super.initWidget(root); + } + + @Override + protected void onItemClick(ApplyUser applyUser, int position) { + ApplyUser user = mAdapter.getItem(position); + if (user == null || user.getId() == 0) { + SimplexToast.show(mContext, "用户不存在"); + return; + } + OtherUserHomeActivity.show(mContext, user.getId()); + } + + @Override + public void showAddRelationSuccess(int relation, int position) { + ApplyUser applyUser = mAdapter.getItem(position); + applyUser.setRelation(relation); + mAdapter.updateItem(position); + hideDialog(); + TDevice.hideSoftKeyboard(mRoot); + } + + @Override + public void showAddRelationError() { + SimplexToast.show(mContext, "关注失败"); + hideDialog(); + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new ApplyAdapter(this, mRelationListener); + } + + private void showDialog(String message) { + if (mProgressDialog == null) + mProgressDialog = DialogHelper.getProgressDialog(mContext); + mProgressDialog.setMessage(message); + mProgressDialog.setCancelable(false); + mProgressDialog.show(); + } + + private void hideDialog() { + if (mProgressDialog == null) + return; + mProgressDialog.dismiss(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyPresenter.java b/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..0bab04dc94b38c69fac10777b2c4e251a3709e3a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/apply/ApplyPresenter.java @@ -0,0 +1,161 @@ +package net.oschina.app.improve.detail.apply; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.ApplyUser; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.UserRelation; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2016/12/27. + */ + +class ApplyPresenter implements ApplyContract.Presenter { + private final ApplyContract.View mView; + private final ApplyContract.EmptyView mEmptyView; + private final long mSourceId; + private final Type mGsonType; + private String mPageToken; + private String mFilter; + + ApplyPresenter(ApplyContract.View mView, ApplyContract.EmptyView mEmptyView, long sourceId) { + this.mView = mView; + this.mSourceId = sourceId; + this.mEmptyView = mEmptyView; + mGsonType = new TypeToken>>() { + }.getType(); + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + OSChinaApi.getApplyUsers(mSourceId, null, mFilter, new TextHttpResponseHandler() { + @Override + public void onFinish() { + super.onFinish(); + mView.onComplete(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.tip_network_error); + if (TextUtils.isEmpty(mPageToken)) + mEmptyView.showGetApplyUserError("网络错误"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> resultBean = new Gson().fromJson(responseString, mGsonType); + List items; + if (resultBean != null) { + if (resultBean.isSuccess()) { + mPageToken = resultBean.getResult().getNextPageToken(); + items = resultBean.getResult().getItems(); + mView.onRefreshSuccess(items); + if (items.size() < 20) + mView.showNotMore(); + mEmptyView.showGetApplyUserSuccess(); + } else { + mView.showNetworkError(R.string.tip_network_error); + if (TextUtils.isEmpty(mPageToken)) + mEmptyView.showGetApplyUserError(resultBean.getMessage()); + if (!TextUtils.isEmpty(mFilter)) { + mEmptyView.showSearchError(resultBean.getMessage()); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void onLoadMore() { + OSChinaApi.getApplyUsers(mSourceId, mPageToken, mFilter, new TextHttpResponseHandler() { + @Override + public void onFinish() { + super.onFinish(); + mView.onComplete(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.tip_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> resultBean = new Gson().fromJson(responseString, mGsonType); + List items; + if (resultBean != null) { + if (resultBean.isSuccess()) { + mPageToken = resultBean.getResult().getNextPageToken(); + items = resultBean.getResult().getItems(); + mView.onLoadMoreSuccess(items); + if (items.size() < 20) + mView.showNotMore(); + } else { + mView.showNetworkError(R.string.tip_network_error); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void addRelation(long authorId, final int position) { + OSChinaApi.addUserRelationReverse(authorId, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showAddRelationError(); + mView.showNetworkError(R.string.tip_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean != null && resultBean.isSuccess()) { + int relation = resultBean.getResult().getRelation(); + mView.showAddRelationSuccess(relation, position); + } else { + mView.showAddRelationError(); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showAddRelationError(); + } + } + }); + } + + void setFilter(String mFilter) { + this.mFilter = mFilter; + if (TextUtils.isEmpty(this.mFilter)) + mPageToken = null; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/db/API.java b/app/src/main/java/net/oschina/app/improve/detail/db/API.java new file mode 100644 index 0000000000000000000000000000000000000000..a922ef3ed723428c452841a581b225e9b81590ea --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/db/API.java @@ -0,0 +1,29 @@ +package net.oschina.app.improve.detail.db; + +import android.text.TextUtils; + +import com.loopj.android.http.AsyncHttpClient; +import com.loopj.android.http.RequestParams; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.BuildConfig; + +/** + * 阅读习惯接口 + * Created by haibin on 2017/5/23. + */ +public final class API { + private static final String URL = "http://192.168.2.64/action/apiv2/push_read_record"; + + public static void addBehaviors(String json, TextHttpResponseHandler handler) { + if(!TextUtils.isEmpty(json)){ + // TODO: 2017/11/6 目前不上传数据 + return; + } + RequestParams params = new RequestParams("json", json); + AsyncHttpClient client = new AsyncHttpClient(); + client.addHeader("passcode", BuildConfig.VIOLET_PASSCODE); + client.addHeader("AppToken", "123456"); + client.post(URL, params, handler); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/db/Behavior.java b/app/src/main/java/net/oschina/app/improve/detail/db/Behavior.java new file mode 100644 index 0000000000000000000000000000000000000000..6c9b69bc3c3eb364a9c224233eb6ea712e0c06b4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/db/Behavior.java @@ -0,0 +1,284 @@ +package net.oschina.app.improve.detail.db; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/** + * 用户习惯收集字段 + * Created by haibin on 2017/5/22. + */ +@SuppressWarnings("all") +@Table(tableName = "behavior") +public class Behavior implements Serializable { + + @PrimaryKey(autoincrement = true, column = "id") + @SerializedName("index") + private int id; + + /** + * 设备唯一编码 + */ + @Column(column = "uuid") + private String uuid; + + /** + * 文章链接 + */ + @Column(column = "url", isNotNull = true) + private String url; + + /** + * 用户id + */ + @Column(column = "user", isNotNull = true) + private long user; + + /** + * 用户名 + */ + @SerializedName("user_name") + @Column(column = "user_name", isNotNull = true) + private String userName; + + /** + * + */ + @Column(column = "operation", isNotNull = true) + private String operation; + + /** + * 操作时间 + */ + @SerializedName("operate_time") + @Column(column = "operate_time", isNotNull = true) + private long operateTime; + + /** + * 文章类型 + */ + @SerializedName("operate_type") + @Column(column = "operate_type", isNotNull = true) + private int operateType; + + /** + * 是否评论 + */ + @SerializedName("is_comment") + @Column(column = "is_comment") + private int isComment; + + /** + * 是否赞 + */ + @SerializedName("is_voteup") + @Column(column = "is_voteup") + private int isVoteup; + + /** + * 是否收藏 + */ + @SerializedName("is_collect") + @Column(column = "is_collect") + private int isCollect; + + /** + * 是否分享 + */ + @SerializedName("is_share") + @Column(column = "is_share") + private int isShare; + + /** + * 网络 + */ + @Column(column = "network") + private String network; + + /** + * 阅读时间 + */ + @Column(column = "stay") + private long stay; + + /** + * 地理位置 + */ + @Column(column = "location") + private String location; + + /** + * 设备,Android、IOS + */ + @Column(column = "device", isNotNull = true) + private String device; + + /** + * 操作系统版本 MEIZU PRO6s 6.0.1 + */ + @Column(column = "os", isNotNull = true) + private String os; + + /** + * app版本号 + */ + @Column(column = "version", isNotNull = true) + private String version; + + @SerializedName("key_str") + @Column(column = "key_str", isNotNull = true) + private String key; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public long getUser() { + return user; + } + + public void setUser(long user) { + this.user = user; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + + public long getOperateTime() { + return operateTime; + } + + public void setOperateTime(long operateTime) { + this.operateTime = operateTime; + } + + public int getIsComment() { + return isComment; + } + + public void setIsComment(int isComment) { + this.isComment = isComment; + } + + public int getIsVoteup() { + return isVoteup; + } + + public void setIsVoteup(int isVoteup) { + this.isVoteup = isVoteup; + } + + public int getIsCollect() { + return isCollect; + } + + public void setIsCollect(int isCollect) { + this.isCollect = isCollect; + } + + public int getIsShare() { + return isShare; + } + + public void setIsShare(int isShare) { + this.isShare = isShare; + } + + public String getNetwork() { + return network; + } + + public void setNetwork(String network) { + this.network = network; + } + + public long getStay() { + return stay; + } + + public void setStay(long stay) { + this.stay = stay; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public String getDevice() { + return device; + } + + public void setDevice(String device) { + this.device = device; + } + + public String getOs() { + return os; + } + + public void setOs(String os) { + this.os = os; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + public int getOperateType() { + return operateType; + } + + public void setOperateType(int operateType) { + this.operateType = operateType; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/db/Column.java b/app/src/main/java/net/oschina/app/improve/detail/db/Column.java new file mode 100644 index 0000000000000000000000000000000000000000..bcb25dcebe96ff95730ce6700147c353555a301e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/db/Column.java @@ -0,0 +1,18 @@ +package net.oschina.app.improve.detail.db; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 字段 + * Created by haibin on 2017/5/23. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Column { + String column(); + + boolean isNotNull() default false; +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/db/DBHelper.java b/app/src/main/java/net/oschina/app/improve/detail/db/DBHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..55e4b6ddfbb1a5297a6d150458824751e4572717 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/db/DBHelper.java @@ -0,0 +1,524 @@ +package net.oschina.app.improve.detail.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.text.TextUtils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/** + * 数据库 + * Created by haibin on 2017/5/22. + */ +@SuppressWarnings("all") +class DBHelper extends SQLiteOpenHelper { + private static final String DB_NAME = "detail.db"; + private static final int DB_VERSION = 1; + + DBHelper(Context context) { + super(context, DB_NAME, null, DB_VERSION); + } + + DBHelper(Context context, String name) { + super(context, name, null, DB_VERSION); + } + + + @Override + public void onCreate(SQLiteDatabase db) { + + } + + private String getTableName(Class cla) { + Annotation[] annotations = cla.getDeclaredAnnotations(); + if (annotations == null || annotations.length == 0) { + throw new IllegalStateException("you must use Table annotation for bean"); + } + String tableName = null; + for (Annotation annotation : annotations) { + if (annotation instanceof Table) + tableName = ((Table) annotation).tableName(); + } + if (TextUtils.isEmpty(tableName)) { + throw new IllegalStateException("you must use Table annotation for bean"); + } + return tableName; + } + + public void create(Class cls) { + String tableName = getTableName(cls); + create(tableName, cls); + } + + public void create(String tableName, Class cls) { + + String sql = "CREATE TABLE IF NOT EXISTS " + tableName + " "; + String table = ""; + String primary = ""; + Field[] fields = cls.getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + if (field.isAnnotationPresent(PrimaryKey.class)) { + PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class); + boolean isAutoincrement = primaryKey.autoincrement(); + String name = primaryKey.column(); + primary = String.format(name + " %s primary key " + + (isAutoincrement ? "autoincrement," : ","), getTypeString(field)); + } else if (field.isAnnotationPresent(Column.class)) { + Column column = field.getAnnotation(Column.class); + String name = column.column(); + table = table + String.format(name + " %s,", getTypeString(field)); + } + } + if (TextUtils.isEmpty(table)) + return; + sql = sql + "(" + primary + table.substring(0, table.length() - 1) + ")"; + getWritableDatabase().execSQL(sql); + } + + public boolean update(Object obj, String tableName, String where, String[] args) { + if (!isExist(tableName)) { + return false; + } + Class cls = obj.getClass(); + SQLiteDatabase db ; + ContentValues values = new ContentValues(); + Field[] fields = cls.getDeclaredFields(); + try { + db = getWritableDatabase(); + for (Field field : fields) { + field.setAccessible(true); + if (field.isAnnotationPresent(Column.class)) { + Column column = field.getAnnotation(Column.class); + + String name = column.column(); + Object object = field.get(obj); + values.put(name, object == null ? "" : object.toString()); + } else if (field.isAnnotationPresent(PrimaryKey.class)) { + PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class); + boolean isAutoincrement = primaryKey.autoincrement(); + String name = primaryKey.column(); + if (!isAutoincrement) { + Object object = field.get(obj); + values.put(name, object == null ? "" : object.toString()); + } + } + } + db.update(tableName, values, where, args); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + public boolean update(Object obj, String where, String... args) { + Class cls = obj.getClass(); + String tableName = getTableName(cls); + return update(obj, tableName, where, args); + } + + public boolean insert(Object obj, String tableName) { + if (!isExist(tableName)) { + return false; + } + Class cls = obj.getClass(); + SQLiteDatabase db; + ContentValues values = new ContentValues(); + Field[] fields = cls.getDeclaredFields(); + try { + db = getWritableDatabase(); + for (Field field : fields) { + field.setAccessible(true); + if (field.isAnnotationPresent(Column.class)) { + Column column = field.getAnnotation(Column.class); + String name = column.column(); + Object object = field.get(obj); + values.put(name, object == null ? "" : object.toString()); + } else if (field.isAnnotationPresent(PrimaryKey.class)) { + PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class); + boolean isAutoincrement = primaryKey.autoincrement(); + String name = primaryKey.column(); + if (!isAutoincrement) { + Object object = field.get(obj); + values.put(name, object == null ? "" : object.toString()); + } + } + } + return db.insert(tableName, "", values) != 0; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + + /** + * 事务插入 + * + * @param lists 数据 + * @param tableName tableName + * @return + */ + private boolean insertList(List lists, String tableName) { + if (!isExist(tableName)) { + return false; + } + SQLiteDatabase db = getWritableDatabase(); + db.beginTransaction(); + try { + for (Object obj : lists) { + Class cls = obj.getClass(); + ContentValues values = new ContentValues(); + Field[] fields = cls.getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + if (field.isAnnotationPresent(Column.class)) { + Column column = field.getAnnotation(Column.class); + String name = column.column(); + Object object = field.get(obj); + values.put(name, object == null ? "" : object.toString()); + } else if (field.isAnnotationPresent(PrimaryKey.class)) { + PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class); + boolean isAutoincrement = primaryKey.autoincrement(); + String name = primaryKey.column(); + if (!isAutoincrement) { + Object object = field.get(obj); + values.put(name, object == null ? "" : object.toString()); + } + } + } + db.insert(tableName, "", values); + } + db.setTransactionSuccessful(); + } catch (Exception e) { + e.printStackTrace(); + return false; + } finally { + if (db.isOpen()) { + db.endTransaction(); + } + } + return true; + } + + + public boolean insert(Object obj) { + Class cls = obj.getClass(); + String tableName = getTableName(cls); + return isExist(tableName) && insert(obj, tableName); + } + + + public boolean insertTransaction(List list, String tableName) { + return isExist(tableName) && insertList(list, tableName); + } + + public long getCount(Class cls) { + String tableName = getTableName(cls); + if (!isExist(tableName)) + return -1; + SQLiteDatabase db; + Cursor cursor = null; + try { + db = getReadableDatabase(); + cursor = db.rawQuery(String.format("select count(*) from %s", tableName), null); + cursor.moveToFirst(); + long count = cursor.getLong(0); + cursor.close(); + return count; + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + return 0; + } + + /** + * 判断数据是否存在 + * + * @return isDataExist + */ + @SuppressWarnings("LoopStatementThatDoesntLoop") + public boolean isDataExist(String tableName, String where) { + SQLiteDatabase db = getWritableDatabase(); + Cursor cursor = null; + try { + String sql = String.format("SELECT * FROM %s %s", tableName, where); + cursor = db.rawQuery(sql, null); + while (cursor.moveToNext()) { + return true;// //有城市在数据库已存在,返回true + } + } catch (Exception e) { + e.printStackTrace(); + return false; + } finally { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + return false; + } + + public boolean clear(String tableName) { + if (!isExist(tableName)) { + return false; + } + SQLiteDatabase db = null; + try { + db = getReadableDatabase(); + db.execSQL(String.format("DELETE from '%s'", tableName)); + return true; + } catch (Exception e) { + e.printStackTrace(); + } finally { + if(db!= null && db.isOpen()){ + db.close(); + } + } + return false; + } + + /** + * 更新字段 + * + * @param table 表 + * @param column 字段 + * @param value 要更新的值 + * @return 成功或者失败 + */ + public boolean update(String table, String column, Object value, String where) { + if (!isExist(table)) { + return false; + } + SQLiteDatabase db; + try { + db = getReadableDatabase(); + String sql = String.format("UPDATE %s SET %s='%s' %s", table, column, value.toString(), where); + db.execSQL(sql); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 新增字段 + * + * @param tableName tableName + * @param columnName columnName + * @param type type + * @return true or false + */ + private boolean alter(String tableName, String columnName, String type) { + if (!isExist(tableName)) return false; + SQLiteDatabase db ; + try { + db = getWritableDatabase(); + db.execSQL(String.format("ALTER TABLE %s ADD %s %s", tableName, columnName, type)); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 新增字段 + * + * @param cls cls + * @return true or false + */ + public boolean alter(Class cls) { + String tableName = getTableName(cls); + if (!isExist(tableName)) + return false; + Field[] fields = cls.getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + if (field.isAnnotationPresent(Column.class)) { + Column column = field.getAnnotation(Column.class); + String name = column.column(); + if (!isColumnExist(tableName, name)) { + alter(tableName, name, getTypeString(field)); + } + } + } + return false; + } + + private boolean isColumnExist(String tableName, String columnName) { + boolean result = false; + Cursor cursor = null; + SQLiteDatabase db ; + try { + db = getReadableDatabase(); + cursor = db.rawQuery("SELECT * FROM " + tableName + " LIMIT 0" + , null); + result = cursor != null && cursor.getColumnIndex(columnName) != -1; + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + + return result; + } + + /** + * 判断数据表是否存在 + * + * @param tableName tableName + * @return 判断数据表是否存在 + */ + public boolean isExist(String tableName) { + if (TextUtils.isEmpty(tableName)) { + return false; + } + boolean exits = false; + SQLiteDatabase db; + Cursor cursor = null; + String sql = "select * from sqlite_master where name=" + "'" + tableName + "'"; + try { + db = getReadableDatabase(); + cursor = db.rawQuery(sql, null); + if (cursor.getCount() != 0) { + exits = true; + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + + return exits; + } + + private String getTypeString(Field field) { + Class type = field.getType(); + if (type.equals(int.class)) { + return "integer"; + } else if (type.equals(String.class)) { + return "text"; + } else if (type.equals(long.class)) { + return "long"; + } else if (type.equals(float.class)) { + return "feal"; + } else if (type.equals(double.class)) { + return "feal"; + } + return "varchar"; + } + + public List get(Class cls) { + return get(cls, null, null, null, 0, 0); + } + + public List get(Class cls, String where) { + return get(cls, where, null, null, 0, 0); + } + + + public List get(Class cls, int limit, int offset) { + return get(cls, null, null, null, limit, offset); + } + + public List get(Class cls, String where, String orderColumn, String orderType, int limit, int offset) { + String tableName = getTableName(cls); + if (!isExist(tableName)) + return null; + List list = new ArrayList<>(); + SQLiteDatabase db ; + Cursor cursor = null; + try { + db = getReadableDatabase(); + String sql = String.format("SELECT * from %s", tableName); + String whereAre = TextUtils.isEmpty(where) ? null : " " + where; + String orderBy = TextUtils.isEmpty(orderColumn) ? null : String.format(" ORDER BY %s %s", orderColumn, orderType); + String limitStr = limit == 0 ? null : String.format(" limit %s offset %s", String.valueOf(limit), String.valueOf(offset)); + StringBuilder sb = new StringBuilder(); + sb.append(sql); + sb.append(TextUtils.isEmpty(limitStr) ? "" : limitStr); + sb.append(TextUtils.isEmpty(whereAre) ? "" : whereAre); + sb.append(TextUtils.isEmpty(orderBy) ? "" : orderBy); + + sql = sb.toString(); + cursor = db.rawQuery(sql, null); + Field[] fields = cls.getDeclaredFields(); + while (cursor.moveToNext()) { + T t = cls.newInstance(); + for (Field field : fields) { + field.setAccessible(true); + String name = ""; + if (field.isAnnotationPresent(Column.class)) + name = field.getAnnotation(Column.class).column(); + else if (field.isAnnotationPresent(PrimaryKey.class)) + name = field.getAnnotation(PrimaryKey.class).column(); + if (!TextUtils.isEmpty(name)) { + Class type = field.getType(); + if (type.equals(int.class)) { + field.set(t, cursor.getInt(cursor.getColumnIndex(name))); + } else if (type.equals(String.class)) { + field.set(t, cursor.getString(cursor.getColumnIndex(name))); + } else if (type.equals(long.class)) { + field.set(t, cursor.getLong(cursor.getColumnIndex(name))); + } else if (type.equals(float.class)) { + field.set(t, cursor.getFloat(cursor.getColumnIndex(name))); + } else if (type.equals(double.class)) { + field.set(t, cursor.getDouble(cursor.getColumnIndex(name))); + } + } + } + list.add(t); + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + return list; + } + + /** + * 删除数据 + * + * @param cls cls + * @return 成功或者失败 + */ + public boolean delete(Class cls, String where, String... args) { + String tableName = getTableName(cls); + if (!isExist(tableName)) + return false; + SQLiteDatabase db; + try { + db = getWritableDatabase(); + int i = db.delete(tableName, where, args); + return i > 0; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/db/DBManager.java b/app/src/main/java/net/oschina/app/improve/detail/db/DBManager.java new file mode 100644 index 0000000000000000000000000000000000000000..bc3b59b0d254fc1bd92a2d846a3e708427e2d455 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/db/DBManager.java @@ -0,0 +1,93 @@ +package net.oschina.app.improve.detail.db; + +import android.content.Context; +import android.os.Environment; + +import net.oschina.app.AppContext; +import net.oschina.common.utils.StreamUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * 墨记数据库 + * Created by huanghaibin on 2017/8/1. + */ +@SuppressWarnings("unused") +public class DBManager extends DBHelper { + private DBManager(Context context) { + super(context); + } + + private static DBManager mInstance; + private static DBManager mCountryManager; + + public static void init(Context context) { + if (mInstance == null) { + mInstance = new DBManager(context); + } + if (mCountryManager == null) { + mCountryManager = getAssetSQLite(context); + } + } + + private DBManager(Context context, String name) { + super(context, name); + } + + public static DBManager getInstance() { + return mInstance; + } + + public static DBManager getCountryManager() { + if (mCountryManager == null) { + mCountryManager = getAssetSQLite(AppContext.getInstance()); + } + return mCountryManager; + } + + /** + * 打开assets的数据库 + * + * @param context context + * @return SQLiteDatabase + */ + private static DBManager getAssetSQLite(Context context) { + try { + String path = Environment.getDataDirectory().getAbsolutePath() + "/data/" + context.getPackageName() + "/databases/osc_data.db"; + if (!new File(path).exists()) { + InputStream is = context.getAssets().open("data.db"); + inputStreamToFile(is, path); + } + DBManager manager = new DBManager(context, "osc_data.db"); + if (!manager.isExist("city")) { + context.deleteDatabase("osc_data.db"); + InputStream is = context.getAssets().open("data.db"); + inputStreamToFile(is, path); + } + return new DBManager(context, "osc_data.db"); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + + private static void inputStreamToFile(InputStream is, String newPath) { + FileOutputStream fs = null; + try { + int read; + fs = new FileOutputStream(newPath); + byte[] buffer = new byte[1444]; + while ((read = is.read(buffer)) != -1) { + fs.write(buffer, 0, read); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + StreamUtil.close(fs, is); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/db/PrimaryKey.java b/app/src/main/java/net/oschina/app/improve/detail/db/PrimaryKey.java new file mode 100644 index 0000000000000000000000000000000000000000..7e0c7e7ce38ce6159a75303f80fead4ce628e1fa --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/db/PrimaryKey.java @@ -0,0 +1,18 @@ +package net.oschina.app.improve.detail.db; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 主键 + * Created by haibin on 2017/5/23. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface PrimaryKey { + boolean autoincrement() default true; + + String column(); +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/db/Table.java b/app/src/main/java/net/oschina/app/improve/detail/db/Table.java new file mode 100644 index 0000000000000000000000000000000000000000..51d36e1b99b05141ec0489d9dcca7feec1c1abf9 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/db/Table.java @@ -0,0 +1,16 @@ +package net.oschina.app.improve.detail.db; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 表 + * Created by haibin on 2017/5/23. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Table { + String tableName(); +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/general/BlogDetailActivity.java b/app/src/main/java/net/oschina/app/improve/detail/general/BlogDetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..210ca58a6a9b1285c65a5ef7e600b19b8829fb79 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/general/BlogDetailActivity.java @@ -0,0 +1,90 @@ +package net.oschina.app.improve.detail.general; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.detail.db.DBManager; +import net.oschina.app.improve.detail.v2.DetailActivity; +import net.oschina.app.improve.detail.v2.DetailFragment; + +/** + * Created by haibin + * on 2016/11/30. + */ + +public class BlogDetailActivity extends DetailActivity { + public static void show(Context context, SubBean bean) { + Intent intent = new Intent(context, BlogDetailActivity.class); + Bundle bundle = new Bundle(); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + public static void show(Context context, long id) { + Intent intent = new Intent(context, BlogDetailActivity.class); + Bundle bundle = new Bundle(); + SubBean bean = new SubBean(); + bean.setType(News.TYPE_BLOG); + bean.setId(id); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + public static void show(Context context, long id, boolean isFav) { + Intent intent = new Intent(context, BlogDetailActivity.class); + Bundle bundle = new Bundle(); + SubBean bean = new SubBean(); + bean.setType(News.TYPE_BLOG); + bean.setId(id); + bean.setFavorite(isFav); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_blog_detail; + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + } + + @Override + protected DetailFragment getDetailFragment() { + return BlogDetailFragment.newInstance(); + } + + + @SuppressLint("SetTextI18n") + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_blog_detail, menu); +// MenuItem menuItem = menu.findItem(R.id.menu_report); +// DrawableCompat.setTint(menuItem.getIcon(),Color.WHITE); + return true; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == 1 && resultCode == RESULT_OK && mBehavior != null) { + mBehavior.setIsComment(1); + DBManager.getInstance() + .update(mBehavior, "operate_time=?", String.valueOf(mBehavior.getOperateTime())); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/general/BlogDetailFragment.java b/app/src/main/java/net/oschina/app/improve/detail/general/BlogDetailFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..a813a7a35625f3be81e8dc3478f246a8a185a75c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/general/BlogDetailFragment.java @@ -0,0 +1,206 @@ +package net.oschina.app.improve.detail.general; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.ImageSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.UserRelation; +import net.oschina.app.improve.detail.pay.PayDialog; +import net.oschina.app.improve.detail.v2.DetailFragment; +import net.oschina.app.improve.media.Util; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.widget.AutoScrollView; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.StringUtils; + +/** + * Created by haibin + * on 2016/11/30. + */ + +public class BlogDetailFragment extends DetailFragment { + + private PortraitView mImageAvatar; + + private IdentityView mIdentityView; + + private TextView mTextName; + + private TextView mTextPubDate; + + private TextView mTextTitle; + + private TextView mTextAbstract; + + private Button mBtnRelation; + + + private PayDialog mDialog; + + + public static BlogDetailFragment newInstance() { + return new BlogDetailFragment(); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_blog_detail_v2; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mTextTitle = (TextView)mHeaderView.findViewById(R.id.tv_title); + mTextPubDate = (TextView)mHeaderView.findViewById(R.id.tv_pub_date); + mTextName = (TextView)mHeaderView.findViewById(R.id.tv_name); + mTextAbstract = (TextView)mHeaderView.findViewById(R.id.tv_detail_abstract); + mImageAvatar = (PortraitView)mHeaderView.findViewById(R.id.iv_avatar); + mIdentityView = (IdentityView)mHeaderView.findViewById(R.id.identityView); + mBtnRelation = (Button)mHeaderView.findViewById(R.id.btn_relation); + mHeaderView.findViewById(R.id.btn_pay).setOnClickListener(this); + mBtnRelation.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mBean.getAuthor() != null) { + mPresenter.addUserRelation(mBean.getAuthor().getId()); + } + } + }); + mImageAvatar.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mBean != null && mBean.getAuthor() != null) { + OtherUserHomeActivity.show(mContext, mBean.getAuthor()); + } + } + }); + } + + @Override + protected void initData() { + super.initData(); + CACHE_CATALOG = OSChinaApi.CATALOG_BLOG; + } + + @Override + public void onClick(View v) { + if (!AccountHelper.isLogin()) { + LoginActivity.show(this, 1); + return; + } + if (mDialog == null) { + mDialog = new PayDialog(mContext, mBean); + mDialog.setOnPayListener(new PayDialog.OnPayListener() { + @Override + public void onPay(float money, int type) { + mPresenter.payDonate(mBean.getAuthor().getId(), mBean.getId(), money, type); + } + }); + } + mDialog.show(); + } + + @Override + public void showGetDetailSuccess(SubBean bean) { + super.showGetDetailSuccess(bean); + if (mContext == null) return; + Author author = bean.getAuthor(); + mIdentityView.setup(author); + if (author != null) { + mTextName.setText(author.getName()); + mImageAvatar.setup(author); + } + mTextPubDate.setText(StringUtils.formatYearMonthDay(bean.getPubDate())); + mTextTitle.setText(bean.getTitle()); + mTextAbstract.setText(bean.getSummary()); + if (TextUtils.isEmpty(bean.getSummary())) { + mRoot.findViewById(R.id.line3).setVisibility(View.GONE); + mRoot.findViewById(R.id.line4).setVisibility(View.GONE); + mTextAbstract.setVisibility(View.GONE); + } + mBtnRelation.setText(bean.getAuthor().getRelation() < UserRelation.RELATION_ONLY_HER + ? "已关注" : "关注"); + + SpannableStringBuilder spannable = new SpannableStringBuilder(bean.getTitle()); + Resources resources = mContext.getResources(); + int top = Util.dipTopx(mContext, 2); + boolean isToday = StringUtils.isToday(mBean.getPubDate()); + if (isToday) { + spannable.append(" [icon] "); + Drawable originate = resources.getDrawable(R.mipmap.ic_label_today); + if (originate != null) { + originate.setBounds(0, -top, originate.getIntrinsicWidth() + top, originate.getIntrinsicHeight()); + } + ImageSpan imageSpan = new ImageSpan(originate, ImageSpan.ALIGN_BOTTOM); + spannable.setSpan(imageSpan, spannable.length() - 7, spannable.length() - 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + + if (bean.isOriginal()) { + spannable.append(" [icon] "); + Drawable originate = resources.getDrawable(R.mipmap.ic_label_originate); + if (originate != null) { + originate.setBounds(0, -top, originate.getIntrinsicWidth() + top, originate.getIntrinsicHeight()); + } + ImageSpan imageSpan = new ImageSpan(originate, ImageSpan.ALIGN_BOTTOM); + spannable.setSpan(imageSpan, spannable.length() - 7, spannable.length() - 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } else { + spannable.append(" [icon] "); + Drawable originate = resources.getDrawable(R.mipmap.ic_label_reprint); + if (originate != null) { + originate.setBounds(0, -top, originate.getIntrinsicWidth() + top, originate.getIntrinsicHeight()); + } + ImageSpan imageSpan = new ImageSpan(originate, ImageSpan.ALIGN_BOTTOM); + spannable.setSpan(imageSpan, spannable.length() - 7, spannable.length() - 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + + if (bean.isRecommend()) { + spannable.append(" [icon] "); + Drawable recommend = resources.getDrawable(R.mipmap.ic_label_recommend); + if (recommend != null) { + recommend.setBounds(0, -top, recommend.getIntrinsicWidth() + top , recommend.getIntrinsicHeight()); + } + ImageSpan imageSpan = new ImageSpan(recommend, ImageSpan.ALIGN_BOTTOM); + spannable.setSpan(imageSpan, spannable.length() - 7, spannable.length() - 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + mTextTitle.setText(spannable); + } + + @Override + public void showAddRelationSuccess(boolean isRelation, int strId) { + mBtnRelation.setText(isRelation ? "已关注" : "关注"); + SimplexToast.show(mContext, strId); + } + + @Override + protected int getCommentOrder() { + return OSChinaApi.COMMENT_NEW_ORDER; + } + + @Override + protected View getHeaderView() { + return new BlogDetailHeaderView(mContext); + } + + private static class BlogDetailHeaderView extends AutoScrollView { + public BlogDetailHeaderView(Context context) { + super(context); + LayoutInflater.from(context).inflate(R.layout.layout_blod_detail_header, this, true); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/general/EventDetailActivity.java b/app/src/main/java/net/oschina/app/improve/detail/general/EventDetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..6e5f61d9f48cca4d2ed411027b87b979a7ae6a55 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/general/EventDetailActivity.java @@ -0,0 +1,309 @@ +package net.oschina.app.improve.detail.general; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.bean.Event; +import net.oschina.app.improve.bean.EventDetail; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.comment.CommentsActivity; +import net.oschina.app.improve.detail.apply.ApplyActivity; +import net.oschina.app.improve.detail.sign.SignUpActivity; +import net.oschina.app.improve.detail.v2.DetailActivity; +import net.oschina.app.improve.detail.v2.DetailFragment; +import net.oschina.app.improve.user.sign.up.SignUpInfoActivity; +import net.oschina.app.util.UIHelper; + +import java.util.HashMap; +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * Created by haibin + * on 2016/12/15. + */ + +public class EventDetailActivity extends DetailActivity implements View.OnClickListener { + private MenuItem mMenuFav; + + @Bind(R.id.iv_sign) + ImageView mImageSign; + + @Bind(R.id.ll_sign) + LinearLayout mLinearSign; + + @Bind(R.id.toolbar) + Toolbar mToolbar; + + @Bind(R.id.iv_event) + ImageView mImageEvent; + + @Bind(R.id.header_view) + View mHeaderView; + + @Bind(R.id.ll_operate) + LinearLayout mLinerOperate; + + @Bind(R.id.tv_apply_status) + TextView mTextApplyStatus; + + @Bind(R.id.tv_comment) + TextView mTextComment; + + @Bind(R.id.line) + View mLine; + + public static void show(Context context, SubBean bean) { + Intent intent = new Intent(context, EventDetailActivity.class); + Bundle bundle = new Bundle(); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + public static void show(Context context, long id) { + Intent intent = new Intent(context, EventDetailActivity.class); + Bundle bundle = new Bundle(); + SubBean bean = new SubBean(); + bean.setType(News.TYPE_EVENT); + bean.setId(id); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + public static void show(Context context, long id, boolean isFav) { + Intent intent = new Intent(context, EventDetailActivity.class); + Bundle bundle = new Bundle(); + SubBean bean = new SubBean(); + bean.setType(News.TYPE_EVENT); + bean.setId(id); + bean.setFavorite(isFav); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_event_detail_v2; + } + + @Override + protected void initWidget() { + super.initWidget(); + mToolbar.setTitle(""); + mToolbar.setSubtitle(""); + setSupportActionBar(mToolbar); + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + } + + + @OnClick({R.id.lay_comment, R.id.ll_sign}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.lay_comment: + CommentsActivity.show(this, mBean.getId(), mBean.getType(), 2, mBean.getTitle()); + break; + case R.id.ll_sign: + HashMap extra = mBean.getExtra(); + if (!AccountHelper.isLogin()) { + LoginActivity.show(this, 0x02); + return; + } + //非原创会,而且是收费活动,跳转到外部链接 + if (extra != null && getExtraBool(extra.get("isPayEvent")) && getExtraInt(extra.get("eventType")) != 1) { + UIHelper.openExternalBrowser(EventDetailActivity.this, getExtraString(extra.get("feeLink"))); + return; + } + + if (extra != null) { + int eventApplyStatus = getExtraInt(extra.get("eventApplyStatus")); + switch (eventApplyStatus) { + case EventDetail.APPLY_STATUS_AUDIT://已报名 + case EventDetail.APPLY_STATUS_CONFIRMED://已报名 + SignUpInfoActivity.show(this, mBean.getId(), 1); + break; + case EventDetail.APPLY_STATUS_PRESENTED://已出席 + ApplyActivity.show(this, mBean.getId()); + break; + default://报名 + SignUpActivity.show(this, mBean.getId()); + break; + } + } else { + SignUpActivity.show(this, mBean.getId()); + } + break; + } + } + + @Override + protected DetailFragment getDetailFragment() { + return EventDetailFragment.newInstance(); + } + + @Override + public void hideEmptyLayout() { + super.hideEmptyLayout(); + if (isDestroy()) + return; + mHeaderView.setVisibility(View.VISIBLE); + mHeaderView.getLayoutParams().height = 400; + List images = mBean.getImages(); + if (images == null || images.size() == 0) + return; + getImageLoader().load(images.get(0).getHref()).into(mImageEvent); + mImageEvent.setVisibility(View.VISIBLE); + if (mMenuFav != null) + mMenuFav.setIcon(mBean.isFavorite() ? R.mipmap.ic_faved_light_normal : R.mipmap.ic_fav_light_normal); + mLinerOperate.setVisibility(View.VISIBLE); + mLine.setVisibility(View.VISIBLE); + mTextComment.setText(String.format("评论(%s)", mBean.getStatistics().getComment())); + + HashMap extra = mBean.getExtra(); + if (extra != null) { + + //活动状态判断 + int eventStatus = getExtraInt(extra.get("eventStatus")); + switch (eventStatus) { + case Event.STATUS_END: + mTextApplyStatus.setText(getResources().getString(R.string.event_status_end)); + mLinearSign.setEnabled(false); + mTextApplyStatus.setEnabled(false); + mImageSign.setEnabled(false); + break; + case Event.STATUS_ING: + mTextApplyStatus.setText(getResources().getString(R.string.event_apply_status_un_sign)); + break; + case Event.STATUS_SING_UP: + mTextApplyStatus.setText(getResources().getString(R.string.event_status_sing_up)); + mLinearSign.setEnabled(false); + mTextApplyStatus.setEnabled(false); + mImageSign.setEnabled(false); + break; + } + if (eventStatus == Event.STATUS_END || eventStatus == Event.STATUS_SING_UP) { + return; + } + + int eventApplyStatus = getExtraInt(extra.get("eventApplyStatus")); + + int applyStr = 0; + switch (eventApplyStatus) { + case EventDetail.APPLY_STATUS_UN_SIGN: + applyStr = R.string.event_apply_status_un_sign; + break; + case EventDetail.APPLY_STATUS_AUDIT: + applyStr = R.string.event_sign_info; + break; + case EventDetail.APPLY_STATUS_CONFIRMED: + applyStr = R.string.event_sign_info; + break; + case EventDetail.APPLY_STATUS_PRESENTED: + applyStr = R.string.event_apply_status_presented; + break; + case EventDetail.APPLY_STATUS_CANCELED: + applyStr = R.string.event_apply_status_un_sign; + break; + case EventDetail.APPLY_STATUS_REFUSED: + applyStr = R.string.event_apply_status_un_sign; + break; + } + mTextApplyStatus.setText(getResources().getString(applyStr)); + // mTextApplyStatus.setText(getString(getApplyStatusStrId(eventApplyStatus))); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_event_detail, menu); + mMenuFav = menu.getItem(0); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (mImageEvent.getVisibility() == View.GONE) + return false; + switch (item.getItemId()) { + case R.id.menu_share: + toShare(mBean.getTitle(), mBean.getBody(), mBean.getHref()); + break; + case R.id.menu_fav: + mPresenter.favReverse(); + break; + } + return true; + } + + @Override + public void showFavReverseSuccess(boolean isFav, int favCount, int strId) { + if (mMenuFav == null) + return; + mMenuFav.setIcon(isFav ? R.mipmap.ic_faved_light_normal : R.mipmap.ic_fav_light_normal); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == Activity.RESULT_OK && data != null) { + switch (requestCode) { + case 0x01: + mBean.getExtra().put("eventApplyStatus", 1); + mTextApplyStatus.setText(getResources().getString(getApplyStatusStrId(EventDetail.APPLY_STATUS_AUDIT))); + break; + case 0x02: + mBean.getExtra().put("eventApplyStatus", -1); + mTextApplyStatus.setText(getResources().getString(getApplyStatusStrId(EventDetail.APPLY_STATUS_UN_SIGN))); + break; + } + } + } + + public int getApplyStatusStrId(int status) { + int strId = R.string.event_apply_status_un_sign; + switch (status) { + case EventDetail.APPLY_STATUS_UN_SIGN: + strId = R.string.event_apply_status_un_sign; + break; + case EventDetail.APPLY_STATUS_AUDIT: + strId = R.string.event_sign_info; + break; + case EventDetail.APPLY_STATUS_CONFIRMED: + strId = R.string.event_sign_info; + break; + case EventDetail.APPLY_STATUS_PRESENTED: + strId = R.string.event_apply_status_presented; + break; + case EventDetail.APPLY_STATUS_CANCELED: + strId = R.string.event_apply_status_un_sign; + break; + case EventDetail.APPLY_STATUS_REFUSED: + strId = R.string.event_apply_status_un_sign; + break; + } + return strId; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/general/EventDetailFragment.java b/app/src/main/java/net/oschina/app/improve/detail/general/EventDetailFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..ebf77cd152d5634596590c7b43fab84562b3c156 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/general/EventDetailFragment.java @@ -0,0 +1,116 @@ +package net.oschina.app.improve.detail.general; + +import android.annotation.SuppressLint; +import android.text.TextUtils; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.detail.v2.DetailFragment; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.utils.QuickOptionDialogHelper; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.StringUtils; + +import java.util.HashMap; + +import butterknife.Bind; +import butterknife.OnLongClick; + +/** + * Created by haibin + * on 2016/12/15. + */ + +public class EventDetailFragment extends DetailFragment { + + @Bind(R.id.iv_event_img) + ImageView mImageEvent; + + @Bind(R.id.tv_event_title) + TextView mTextTitle; + + @Bind(R.id.tv_event_author) + TextView mTextAuthor; + + @Bind(R.id.tv_event_cost_desc) + TextView mTextCostDesc; + + @Bind(R.id.tv_event_status) + TextView mTextStatus; + + @Bind(R.id.tv_event_start_date) + TextView mTextStartDate; + + @Bind(R.id.tv_event_location) + TextView mTextLocation; + + @Bind(R.id.civ_author) + PortraitView mImageAuthor; + + public static EventDetailFragment newInstance() { + return new EventDetailFragment(); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_event_detail_v2; + } + + @Override + protected void initData() { + super.initData(); + CACHE_CATALOG = OSChinaApi.CATALOG_EVENT; + } + + @SuppressLint("SetTextI18n") + @Override + public void showGetDetailSuccess(SubBean bean) { + super.showGetDetailSuccess(bean); + mTextTitle.setText(bean.getTitle()); + final Author author = bean.getAuthor(); + if (author != null) { + mTextAuthor.setText(author.getName()); + mImageAuthor.setup(author); + mImageAuthor.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + OtherUserHomeActivity.show(mContext, author); + } + }); + } + HashMap extra = bean.getExtra(); + if (extra != null) { + mTextLocation.setText(getExtraString(extra.get("eventProvince")) + " " + + getExtraString(extra.get("eventCity")) + " " + + getExtraString(extra.get("eventSpot"))); + mTextStartDate.setText(StringUtils.getDateString(getExtraString(extra.get("eventStartDate")))); + mTextCostDesc.setText(getExtraString(extra.get("eventCostDesc"))); + } + } + + @Override + public void onPageFinished() { + } + + @Override + protected int getCommentOrder() { + return OSChinaApi.COMMENT_HOT_ORDER; + } + + @OnLongClick(R.id.lay_event_location) + boolean onLongClickLocation() { + final String text = mTextLocation.getText().toString(); + if (TextUtils.isEmpty(text)) + return false; + QuickOptionDialogHelper.with(getContext()) + .addCopy(HTMLUtil.delHTMLTag(text)) + .show(); + return true; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/general/NewsDetailActivity.java b/app/src/main/java/net/oschina/app/improve/detail/general/NewsDetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..2ab497ec68fbd2e8f8e7dcd5afb5a0a57dd30b61 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/general/NewsDetailActivity.java @@ -0,0 +1,73 @@ +package net.oschina.app.improve.detail.general; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.detail.v2.DetailActivity; +import net.oschina.app.improve.detail.v2.DetailFragment; + +/** + * Created by haibin + * on 2016/11/30. + */ + +public class NewsDetailActivity extends DetailActivity { + + public static void show(Context context, SubBean bean) { + Intent intent = new Intent(context, NewsDetailActivity.class); + Bundle bundle = new Bundle(); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + public static void show(Context context, long id, int type) { + Intent intent = new Intent(context, NewsDetailActivity.class); + Bundle bundle = new Bundle(); + SubBean bean = new SubBean(); + bean.setType(type); + bean.setId(id); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + public static void show(Context context, long id) { + Intent intent = new Intent(context, NewsDetailActivity.class); + Bundle bundle = new Bundle(); + SubBean bean = new SubBean(); + bean.setType(News.TYPE_NEWS); + bean.setId(id); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + public static void show(Context context, long id, boolean isFav) { + Intent intent = new Intent(context, NewsDetailActivity.class); + Bundle bundle = new Bundle(); + SubBean bean = new SubBean(); + bean.setType(News.TYPE_NEWS); + bean.setId(id); + bean.setFavorite(isFav); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + } + + @Override + protected DetailFragment getDetailFragment() { + return NewsDetailFragment.newInstance(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/general/NewsDetailFragment.java b/app/src/main/java/net/oschina/app/improve/detail/general/NewsDetailFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..afe134f67f0377496e16d1c697bdd861897fce2f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/general/NewsDetailFragment.java @@ -0,0 +1,117 @@ +package net.oschina.app.improve.detail.general; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.Software; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.detail.v2.DetailFragment; +import net.oschina.app.improve.utils.ReadedIndexCacheManager; +import net.oschina.app.improve.widget.AutoScrollView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; + +/** + * Created by haibin + * on 2016/11/30. + */ + +public class NewsDetailFragment extends DetailFragment { + private TextView mTextTitle; + + private TextView mTextPubDate; + + private TextView mTextAuthor; + + private PortraitView mPortraitView; + + private TextView mTextSoftwareName; + private LinearLayout mLinearSoftwareRoot; + + public static NewsDetailFragment newInstance() { + return new NewsDetailFragment(); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_news_detail_v2; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mTextTitle = (TextView) mHeaderView.findViewById(R.id.tv_title); + mTextPubDate = (TextView) mHeaderView.findViewById(R.id.tv_pub_date); + mTextAuthor = (TextView) mHeaderView.findViewById(R.id.tv_author); + mPortraitView = (PortraitView) mHeaderView.findViewById(R.id.iv_avatar); + LinearLayout mLinearSoftware = (LinearLayout) mHeaderView.findViewById(R.id.ll_software); + mLinearSoftwareRoot = (LinearLayout) mHeaderView.findViewById(R.id.ll_software_root); + mTextSoftwareName = (TextView) mHeaderView.findViewById(R.id.tv_software_name); + mLinearSoftware.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mBean != null && mBean.getSoftware() != null) + SoftwareDetailActivity.show(mContext, mBean.getSoftware().getId()); + } + }); + } + + @Override + protected void initData() { + super.initData(); + CACHE_CATALOG = OSChinaApi.CATALOG_NEWS; + } + + @SuppressLint("SetTextI18n") + @Override + public void showGetDetailSuccess(SubBean bean) { + super.showGetDetailSuccess(bean); + mTextTitle.setText(bean.getTitle()); + mTextPubDate.setText(StringUtils.formatYearMonthDay(bean.getPubDate())); + Author author = mBean.getAuthor(); + if (author != null) { + mTextAuthor.setText(author.getName()); + } + mPortraitView.setup(author); + Software software = bean.getSoftware(); + if (software != null) { + mLinearSoftwareRoot.setVisibility(View.VISIBLE); + mTextSoftwareName.setText(software.getName()); + } else { + mLinearSoftwareRoot.setVisibility(View.GONE); + } + } + + @Override + protected int getCommentOrder() { + return OSChinaApi.COMMENT_HOT_ORDER; + } + + @Override + public void onDestroy() { + if (mBean != null && mBean.getId() > 0) { + ReadedIndexCacheManager.saveIndex(getContext(), mBean.getId(), OSChinaApi.CATALOG_NEWS, + mViewScroller.getScrollY()); + } + super.onDestroy(); + } + + @Override + protected View getHeaderView() { + return new NewsDetailHeaderView(mContext); + } + + private static class NewsDetailHeaderView extends AutoScrollView { + public NewsDetailHeaderView(Context context) { + super(context); + LayoutInflater.from(context).inflate(R.layout.layout_news_detail_header, this, true); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/general/QuestionDetailActivity.java b/app/src/main/java/net/oschina/app/improve/detail/general/QuestionDetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..3480115aae99841f8c6e11cfcd1ae5085651dc9a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/general/QuestionDetailActivity.java @@ -0,0 +1,83 @@ +package net.oschina.app.improve.detail.general; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; + +import net.oschina.app.R; +import net.oschina.app.bean.Report; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.detail.v2.DetailActivity; +import net.oschina.app.improve.detail.v2.DetailFragment; +import net.oschina.app.improve.detail.v2.ReportDialog; + +/** + * Created by haibin + * on 2016/12/15. + */ + +public class QuestionDetailActivity extends DetailActivity { + public static void show(Context context, SubBean bean) { + Intent intent = new Intent(context, QuestionDetailActivity.class); + Bundle bundle = new Bundle(); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + public static void show(Context context, long id) { + Intent intent = new Intent(context, QuestionDetailActivity.class); + Bundle bundle = new Bundle(); + SubBean bean = new SubBean(); + bean.setType(News.TYPE_QUESTION); + bean.setId(id); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + public static void show(Context context, long id, boolean isFav) { + Intent intent = new Intent(context, QuestionDetailActivity.class); + Bundle bundle = new Bundle(); + SubBean bean = new SubBean(); + bean.setType(News.TYPE_QUESTION); + bean.setId(id); + bean.setFavorite(isFav); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void initWidget() { + mCommentHint = "我要回答"; + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + } + + + @SuppressLint("SetTextI18n") + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_question_detail, menu); +// MenuItem menuItem = menu.findItem(R.id.menu_report); +// DrawableCompat.setTint(menuItem.getIcon(),Color.WHITE); + return true; + } + + + @Override + protected DetailFragment getDetailFragment() { + return QuestionDetailFragment.newInstance(); + } + + + protected void toReport(long id, String href) { + ReportDialog.create(this, id, href, Report.TYPE_QUESTION,"").show(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/general/QuestionDetailFragment.java b/app/src/main/java/net/oschina/app/improve/detail/general/QuestionDetailFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..a9491ec1cdee7b18fa2ae48a75d53f29a1432070 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/general/QuestionDetailFragment.java @@ -0,0 +1,127 @@ +package net.oschina.app.improve.detail.general; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.UserRelation; +import net.oschina.app.improve.detail.v2.DetailFragment; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.widget.AutoScrollView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.StringUtils; + +/** + * Created by haibin + * on 2016/12/15. + */ + +public class QuestionDetailFragment extends DetailFragment { + TextView mTextTitle; + + TextView mTextAuthor; + + TextView mTextPubDate; + + Button mBtnRelation; + + PortraitView mImageAvatar; + + public static QuestionDetailFragment newInstance() { + return new QuestionDetailFragment(); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_question_detail_v2; + } + + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mImageAvatar = (PortraitView) mHeaderView.findViewById(R.id.iv_avatar); + mBtnRelation = (Button) mHeaderView.findViewById(R.id.btn_relation); + mTextTitle = (TextView) mHeaderView.findViewById(R.id.tv_title); + mTextAuthor = (TextView) mHeaderView.findViewById(R.id.tv_author); + mTextPubDate = (TextView) mHeaderView.findViewById(R.id.tv_pub_date); + mTextTitle.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + showCopyTitle(); + return true; + } + }); + } + + @Override + protected void initData() { + super.initData(); + CACHE_CATALOG = OSChinaApi.CATALOG_QUESTION; + mImageAvatar.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mBean != null && mBean.getAuthor() != null) { + OtherUserHomeActivity.show(mContext, mBean.getAuthor()); + } + } + }); + mBtnRelation.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mBean.getAuthor() != null) { + mPresenter.addUserRelation(mBean.getAuthor().getId()); + } + } + }); + } + + + @Override + public void showGetDetailSuccess(SubBean bean) { + super.showGetDetailSuccess(bean); + mTextTitle.setText(bean.getTitle()); + if (bean.getAuthor() != null) + mTextAuthor.setText(bean.getAuthor().getName()); + mTextPubDate.setText(StringUtils.formatYearMonthDay(bean.getPubDate())); + mBtnRelation.setText(bean.getAuthor().getRelation() < UserRelation.RELATION_ONLY_HER + ? "已关注" : "关注"); + + Author author = bean.getAuthor(); + if (author != null) { + mTextAuthor.setText(author.getName()); + mImageAvatar.setup(author); + } + } + + @Override + public void showAddRelationSuccess(boolean isRelation, int strId) { + mBtnRelation.setText(isRelation ? "已关注" : "关注"); + SimplexToast.show(mContext, strId); + } + + @Override + protected int getCommentOrder() { + return OSChinaApi.COMMENT_NEW_ORDER; + } + + + @Override + protected View getHeaderView() { + return new QuestionDetailHeaderView(mContext); + } + + private static class QuestionDetailHeaderView extends AutoScrollView { + public QuestionDetailHeaderView(Context context) { + super(context); + LayoutInflater.from(context).inflate(R.layout.layout_question_detail_header, this, true); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/general/SoftwareDetailActivity.java b/app/src/main/java/net/oschina/app/improve/detail/general/SoftwareDetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..0050881e0425d04d53cc2e0d4b1486aa977a1665 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/general/SoftwareDetailActivity.java @@ -0,0 +1,90 @@ +package net.oschina.app.improve.detail.general; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.detail.v2.DetailActivity; +import net.oschina.app.improve.detail.v2.DetailFragment; + +/** + * Created by haibin + * on 2016/12/15. + */ + +public class SoftwareDetailActivity extends DetailActivity { + + public static void show(Context context, SubBean bean) { + Intent intent = new Intent(context, SoftwareDetailActivity.class); + Bundle bundle = new Bundle(); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + public static void show(Context context, String ident) { + Intent intent = new Intent(context, SoftwareDetailActivity.class); + Bundle bundle = new Bundle(); + SubBean bean = new SubBean(); + bean.setType(1); + bundle.putSerializable("sub_bean", bean); + bundle.putString("ident", ident); + intent.putExtras(bundle); + context.startActivity(intent); + } + + + public static void show(Context context, long id) { + Intent intent = new Intent(context, SoftwareDetailActivity.class); + Bundle bundle = new Bundle(); + SubBean bean = new SubBean(); + bean.setId(id); + bean.setType(1); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + public static void show(Context context, long id, boolean isFav) { + Intent intent = new Intent(context, SoftwareDetailActivity.class); + Bundle bundle = new Bundle(); + SubBean bean = new SubBean(); + bean.setId(id); + bean.setType(1); + bean.setFavorite(isFav); + bundle.putSerializable("sub_bean", bean); + intent.putExtras(bundle); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_software_detail; + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void initWidget() { + mCommentHint = "我要评论"; + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + } + + @SuppressLint("SetTextI18n") + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_blog_detail, menu); + return true; + } + + + @Override + protected DetailFragment getDetailFragment() { + return SoftwareDetailFragment.newInstance(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/general/SoftwareDetailFragment.java b/app/src/main/java/net/oschina/app/improve/detail/general/SoftwareDetailFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..c544e9b32e94a1ebc3dee98120ca888de3fd9a6a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/general/SoftwareDetailFragment.java @@ -0,0 +1,146 @@ +package net.oschina.app.improve.detail.general; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.detail.v2.DetailFragment; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.widget.AutoScrollView; +import net.oschina.app.util.UIHelper; + +import java.util.List; +import java.util.Map; + +/** + * Created by haibin + * on 2016/12/15. + */ + +public class SoftwareDetailFragment extends DetailFragment { + + ImageView mImageRecommend; + ImageView mImageSoftware; + + TextView mTextName; + + TextView mTextAuthor; + + TextView mTextProtocol; + + TextView mTextLanguage; + + TextView mTextSystem; + + TextView mTextRecordTime; + + LinearLayout mLinearAvatar; + + public static SoftwareDetailFragment newInstance() { + return new SoftwareDetailFragment(); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_software_detail_v2; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mImageRecommend = (ImageView) mHeaderView.findViewById(R.id.iv_label_recommend); + mImageSoftware = (ImageView) mHeaderView.findViewById(R.id.iv_software_icon); + mTextName = (TextView) mHeaderView.findViewById(R.id.tv_software_name); + mTextAuthor = (TextView) mHeaderView.findViewById(R.id.tv_software_author_name); + mTextProtocol = (TextView) mHeaderView.findViewById(R.id.tv_software_protocol); + mTextLanguage = (TextView) mHeaderView.findViewById(R.id.tv_software_language); + mTextSystem = (TextView) mHeaderView.findViewById(R.id.tv_software_system); + mTextRecordTime = (TextView) mHeaderView.findViewById(R.id.tv_software_record_time); + mLinearAvatar = (LinearLayout) mHeaderView.findViewById(R.id.ll_avatar); + mTextAuthor.setOnClickListener(this); + mHeaderView.findViewById(R.id.tv_home).setOnClickListener(this); + mHeaderView.findViewById(R.id.tv_document).setOnClickListener(this); + } + + @Override + protected void initData() { + super.initData(); + CACHE_CATALOG = OSChinaApi.CATALOG_SOFTWARE; + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.tv_home: + case R.id.tv_document: + Map extras1 = mBean.getExtra(); + if (extras1 != null) + UIHelper.showUrlRedirect(mContext, getExtraString(extras1.get("softwareHomePage"))); + break; + case R.id.tv_software_author_name: + Author author = mBean.getAuthor(); + if (author == null) return; + OtherUserHomeActivity.show(getActivity(), author); + break; + } + } + + @SuppressLint({"DefaultLocale", "SetTextI18n"}) + @Override + public void showGetDetailSuccess(SubBean bean) { + super.showGetDetailSuccess(bean); + if (mContext == null) + return; + + mImageRecommend.setVisibility(bean.isRecommend() ? View.VISIBLE : View.INVISIBLE); + List images = bean.getImages(); + if (images != null && images.size() != 0) + getImgLoader().load(images.get(0).getHref()).asBitmap().into(mImageSoftware); + + Author author = bean.getAuthor(); + if (author != null) { + mTextAuthor.setText(author.getName()); + } else { + mLinearAvatar.setVisibility(View.GONE); + mTextAuthor.setText("匿名"); + } + Map extras = bean.getExtra(); + if (extras != null) { + mTextName.setText(getExtraString(extras.get("softwareTitle")) + " " + getExtraString(extras.get("softwareName"))); + String protocol = getExtraString(extras.get("softwareLicense")); + if (TextUtils.isEmpty(protocol)) + protocol = "未知"; + mTextProtocol.setText(protocol); + mTextRecordTime.setText(getExtraString(extras.get("softwareCollectionDate"))); + mTextSystem.setText(getExtraString(extras.get("softwareSupportOS"))); + mTextLanguage.setText(getExtraString(extras.get("softwareLanguage"))); + } + } + + @Override + protected int getCommentOrder() { + return OSChinaApi.COMMENT_HOT_ORDER; + } + + + @Override + protected View getHeaderView() { + return new SoftwareDetailHeaderView(mContext); + } + + private static class SoftwareDetailHeaderView extends AutoScrollView { + public SoftwareDetailHeaderView(Context context) { + super(context); + LayoutInflater.from(context).inflate(R.layout.layout_software_detail_header, this, true); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/pay/PayDialog.java b/app/src/main/java/net/oschina/app/improve/detail/pay/PayDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..8b00cbfd03d916cbdc0a0d79276e8d216ffb3c5a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/pay/PayDialog.java @@ -0,0 +1,170 @@ +package net.oschina.app.improve.detail.pay; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.RadioGroup; +import android.widget.TextView; + +import com.bumptech.glide.Glide; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.SimpleTextWatcher; + +import de.hdodenhof.circleimageview.CircleImageView; + +/** + * 支付对话框 + * Created by huanghaibin on 2017/6/16. + */ + +public class PayDialog extends Dialog implements View.OnClickListener { + + + private SubBean mBean; + private EditText mEditInput; + private Button mBtnPay; + private OnPayListener mListener; + private float mMoney; + private int mPayType = 1; + + public PayDialog(@NonNull Context context, SubBean bean) { + super(context); + this.mBean = bean; + } + + public void setOnPayListener(OnPayListener listener) { + this.mListener = listener; + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void onCreate(Bundle savedInstanceState) { + requestWindowFeature(Window.FEATURE_NO_TITLE); + super.onCreate(savedInstanceState); + setContentView(R.layout.dialog_pay); + + WindowManager.LayoutParams params = getWindow().getAttributes(); + params.width = getWindow().getWindowManager().getDefaultDisplay().getWidth(); + getWindow().setAttributes(params); + + + CircleImageView mPortrait = (CircleImageView) findViewById(R.id.iv_portrait); + mEditInput = (EditText) findViewById(R.id.et_input); + mBtnPay = (Button) findViewById(R.id.btn_pay); + mBtnPay.setOnClickListener(this); + final TextView mTextPayInfo = (TextView) findViewById(R.id.tv_pay_choice); + findViewById(R.id.tv_type).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + DialogHelper.getSelectDialog(getContext(), + "选择支付方式", + new String[]{"支付宝", "微信"}, + "取消", new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case 0: + mPayType = 1; + mTextPayInfo.setText("使用支付宝支付,"); + break; + case 1: + mPayType = 2; + mTextPayInfo.setText("使用微信支付,"); + break; + } + } + }).show(); + } + }); + + Glide.with(getContext()) + .load(mBean.getAuthor().getPortrait()) + .asBitmap() + .placeholder(R.mipmap.widget_default_face) + .error(R.mipmap.widget_default_face) + .into(mPortrait); + ((TextView) findViewById(R.id.tv_nick_name)).setText(mBean.getAuthor().getName()); + + mBtnPay.setEnabled(false); + mEditInput.addTextChangedListener(new SimpleTextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + super.onTextChanged(s, start, before, count); + if (TextUtils.isEmpty(s)) { + mBtnPay.setEnabled(false); + return; + } + String mCastStr = s.toString(); + float cast; + try { + cast = Float.valueOf(mCastStr); + } catch (Exception e) { + cast = 0; + mEditInput.setText(null); + } + if (cast <= 0) { + mBtnPay.setEnabled(false); + } else { + mMoney = cast; + mBtnPay.setEnabled(true); + } + } + }); + + ((RadioGroup) findViewById(R.id.rg_pay)).setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + switch (checkedId) { + case R.id.rb_five: + mEditInput.setEnabled(false); + mEditInput.setVisibility(View.GONE); + mMoney = 5.00f; + mEditInput.setText(String.valueOf(5)); + break; + case R.id.rb_ten: + mEditInput.setEnabled(false); + mEditInput.setVisibility(View.GONE); + mMoney = 10.00f; + mEditInput.setText(String.valueOf(10)); + break; + case R.id.rb_other: + mEditInput.setEnabled(true); + mEditInput.setVisibility(View.VISIBLE); + break; + } + } + }); + } + + + @Override + public void onClick(View v) { + if (mMoney == 0 || mListener == null) return; + if(mMoney < 1){ + SimplexToast.show(getContext(),"打赏金额最低1元"); + return; + } + if(mMoney> 10000){ + SimplexToast.show(getContext(),"打赏金额不能超过 10000 元"); + return; + } + mListener.onPay(mPayType == 1 ? mMoney * 100: mMoney * 100, mPayType); + dismiss(); + } + + public interface OnPayListener { + void onPay(float money, int type); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/pay/alipay/Alipay.java b/app/src/main/java/net/oschina/app/improve/detail/pay/alipay/Alipay.java new file mode 100644 index 0000000000000000000000000000000000000000..92122edc4a301f0dde5f129b3648ecc9be8d05be --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/pay/alipay/Alipay.java @@ -0,0 +1,99 @@ +package net.oschina.app.improve.detail.pay.alipay; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.os.Handler; +import android.os.Message; +import android.text.TextUtils; +import android.util.Log; + +import com.alipay.sdk.app.PayTask; + +import net.oschina.app.improve.widget.SimplexToast; + +import java.util.Map; + +/** + * 支付宝支付 + * Created by huanghaibin_dev + * on 2016/7/1. + */ +public class Alipay { + + private static final int SDK_PAY_FLAG = 1; + private static final int SDK_CHECK_FLAG = 2; + + private Activity mActivity; + + public Alipay(Activity mActivity) { + this.mActivity = mActivity; + } + + @SuppressLint("HandlerLeak") + private Handler resultHandler = new Handler() { + @SuppressWarnings("unused") + public void handleMessage(Message msg) { + switch (msg.what) { + case SDK_PAY_FLAG: { + @SuppressWarnings("unchecked") + PayResult payResult = new PayResult((Map) msg.obj); + /** + 对于支付结果,请商户依赖服务端的异步通知结果。同步通知结果,仅作为支付结束的通知。 + */ + String resultInfo = payResult.getResult();// 同步返回需要验证的信息 + String resultStatus = payResult.getResultStatus(); + // 判断resultStatus 为9000则代表支付成功 + //Log.e("aaaa"," 88 "+resultInfo); + if (TextUtils.equals(resultStatus, "9000")) { + // 该笔订单是否真实支付成功,需要依赖服务端的异步通知。 + SimplexToast.show(mActivity,"感谢您的支持"); + } else { + // 该笔订单真实的支付结果,需要依赖服务端的异步通知。 + + } + break; + } + case SDK_CHECK_FLAG: { + @SuppressWarnings("unchecked") + AuthResult authResult = new AuthResult((Map) msg.obj, true); + String resultStatus = authResult.getResultStatus(); + ///Log.e("bbb",""+resultStatus); + // 判断resultStatus 为“9000”且result_code + // 为“200”则代表授权成功,具体状态码代表含义可参考授权接口文档 + if (TextUtils.equals(resultStatus, "9000") && TextUtils.equals(authResult.getResultCode(), "200")) { + // 获取alipay_open_id,调支付时作为参数extern_token 的value + // 传入,则支付账户为该授权账户 + + } else { + // 其他状态值则为授权失败 + + } + break; + } + default: + break; + } + } + + ; + }; + + public void payV2(final String orderInfo) { + Runnable payRunnable = new Runnable() { + + @Override + public void run() { + PayTask alipay = new PayTask(mActivity); + Map result = alipay.payV2(orderInfo, true); + + Message msg = new Message(); + msg.what = SDK_PAY_FLAG; + msg.obj = result; + resultHandler.sendMessage(msg); + } + }; + + Thread payThread = new Thread(payRunnable); + payThread.start(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/pay/alipay/AuthResult.java b/app/src/main/java/net/oschina/app/improve/detail/pay/alipay/AuthResult.java new file mode 100644 index 0000000000000000000000000000000000000000..d2c309629a17d464e284a42fde49ce226e3563a1 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/pay/alipay/AuthResult.java @@ -0,0 +1 @@ +package net.oschina.app.improve.detail.pay.alipay; import java.util.Map; import android.text.TextUtils; @SuppressWarnings("unused") public class AuthResult { private String resultStatus; private String result; private String memo; private String resultCode; private String authCode; private String alipayOpenId; AuthResult(Map rawResult, boolean removeBrackets) { if (rawResult == null) { return; } for (String key : rawResult.keySet()) { if (TextUtils.equals(key, "resultStatus")) { resultStatus = rawResult.get(key); } else if (TextUtils.equals(key, "result")) { result = rawResult.get(key); } else if (TextUtils.equals(key, "memo")) { memo = rawResult.get(key); } } String[] resultValue = result.split("&"); for (String value : resultValue) { if (value.startsWith("alipay_open_id")) { alipayOpenId = removeBrackets(getValue("alipay_open_id=", value), removeBrackets); continue; } if (value.startsWith("auth_code")) { authCode = removeBrackets(getValue("auth_code=", value), removeBrackets); continue; } if (value.startsWith("result_code")) { resultCode = removeBrackets(getValue("result_code=", value), removeBrackets); } } } private String removeBrackets(String str, boolean remove) { if (remove) { if (!TextUtils.isEmpty(str)) { if (str.startsWith("\"")) { str = str.replaceFirst("\"", ""); } if (str.endsWith("\"")) { str = str.substring(0, str.length() - 1); } } } return str; } @Override public String toString() { return "resultStatus={" + resultStatus + "};memo={" + memo + "};result={" + result + "}"; } private String getValue(String header, String data) { return data.substring(header.length(), data.length()); } /** * @return the resultStatus */ public String getResultStatus() { return resultStatus; } /** * @return the memo */ public String getMemo() { return memo; } /** * @return the result */ public String getResult() { return result; } /** * @return the resultCode */ public String getResultCode() { return resultCode; } /** * @return the authCode */ public String getAuthCode() { return authCode; } /** * @return the alipayOpenId */ public String getAlipayOpenId() { return alipayOpenId; } } \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/detail/pay/alipay/PayResult.java b/app/src/main/java/net/oschina/app/improve/detail/pay/alipay/PayResult.java new file mode 100644 index 0000000000000000000000000000000000000000..d34f81c3c7cd43cc3ed9cfcfbf107e94b24d7eb2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/pay/alipay/PayResult.java @@ -0,0 +1 @@ +package net.oschina.app.improve.detail.pay.alipay; import java.util.Map; import android.text.TextUtils; public class PayResult { private String resultStatus; private String result; private String memo; PayResult(Map rawResult) { if (rawResult == null) { return; } for (String key : rawResult.keySet()) { if (TextUtils.equals(key, "resultStatus")) { resultStatus = rawResult.get(key); } else if (TextUtils.equals(key, "result")) { result = rawResult.get(key); } else if (TextUtils.equals(key, "memo")) { memo = rawResult.get(key); } } } @Override public String toString() { return "resultStatus={" + resultStatus + "};memo={" + memo + "};result={" + result + "}"; } /** * @return the resultStatus */ String getResultStatus() { return resultStatus; } /** * @return the result */ public String getResult() { return result; } } \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/detail/pay/wx/WeChatPay.java b/app/src/main/java/net/oschina/app/improve/detail/pay/wx/WeChatPay.java new file mode 100644 index 0000000000000000000000000000000000000000..993163bddbadc382297eb3d309a47fd8435ef47a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/pay/wx/WeChatPay.java @@ -0,0 +1,91 @@ +package net.oschina.app.improve.detail.pay.wx; + +import android.app.Activity; + +import com.tencent.mm.sdk.modelpay.PayReq; +import com.tencent.mm.sdk.openapi.IWXAPI; +import com.tencent.mm.sdk.openapi.WXAPIFactory; + +import java.io.Serializable; + +/** + * 微信支付 + * Created by huanghaibin on 2017/6/14. + */ +@SuppressWarnings("all") +public final class WeChatPay { + private IWXAPI msgApi; + + public WeChatPay(Activity mActivity) { + //wxd23e1a82c7c450d1 + msgApi = WXAPIFactory.createWXAPI(mActivity, "wxa8213dc827399101", true); + msgApi.registerApp("wxa8213dc827399101"); + } + + public boolean isWxAppInstalled() { + return msgApi.isWXAppInstalled(); + } + + public void pay(PayResult result) { + PayReq request = new PayReq(); + request.appId = "wxa8213dc827399101"; + request.partnerId = result.getPartnerid(); + request.prepayId = result.getPrepayid(); + request.packageValue = "Sign=WXPay"; + request.nonceStr = result.getNoncestr(); + request.timeStamp = result.getTimestamp(); + request.sign = result.getSign(); + msgApi.sendReq(request); + } + + /** + * 支付信息预拉取 + */ + public static class PayResult implements Serializable { + private String sign; + private String partnerid; + private String prepayid; + private String noncestr; + private String timestamp; + + public String getSign() { + return sign; + } + + public void setSign(String sign) { + this.sign = sign; + } + + public String getPartnerid() { + return partnerid; + } + + public void setPartnerid(String partnerid) { + this.partnerid = partnerid; + } + + public String getPrepayid() { + return prepayid; + } + + public void setPrepayid(String prepayid) { + this.prepayid = prepayid; + } + + public String getNoncestr() { + return noncestr; + } + + public void setNoncestr(String noncestr) { + this.noncestr = noncestr; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/share/ShareActivity.java b/app/src/main/java/net/oschina/app/improve/detail/share/ShareActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..88d4a78a96bfa31d249a3aa19eeeb564811a0dc3 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/share/ShareActivity.java @@ -0,0 +1,144 @@ +package net.oschina.app.improve.detail.share; + +import android.Manifest; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.view.View; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.detail.share.blog.ShareBlogActivity; +import net.oschina.app.improve.detail.share.news.ShareNewsActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.OWebView; + +import java.util.List; + +import butterknife.OnClick; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * 文章详情分享界面 + * Created by huanghaibin on 2017/9/25. + */ + +public abstract class ShareActivity extends BackActivity implements + EasyPermissions.PermissionCallbacks, + View.OnClickListener, + Runnable { + + protected ShareFragment mFragment; + protected OWebView mWebView; + protected SubBean mBean; + private int mType; + private static final int TYPE_SHARE = 1; + private static final int TYPE_SAVE = 2; + + + public static void show(Context context, SubBean bean) { + if (bean == null) + return; + switch (bean.getType()) { + case News.TYPE_BLOG: + ShareBlogActivity.show(context, bean); + break; + case News.TYPE_NEWS: + ShareNewsActivity.show(context, bean); + break; + } + } + + @Override + public void run() { + + } + + @Override + protected int getContentView() { + return R.layout.activity_share; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + } + + @Override + protected void initData() { + super.initData(); + mWebView = (OWebView) findViewById(R.id.webView); + mBean = (SubBean) getIntent().getSerializableExtra("bean"); + mWebView.loadDetailDataAsync(mBean.getBody(),this); + mFragment = getShareFragment(); + addFragment(R.id.fl_content, mFragment); + } + + @OnClick({R.id.ll_share, R.id.ll_save}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.ll_share: + mType = TYPE_SHARE; + saveToFileByPermission(); + break; + case R.id.ll_save: + mType = TYPE_SAVE; + saveToFileByPermission(); + break; + } + } + + private static final int PERMISSION_ID = 0x0001; + + @SuppressWarnings("unused") + @AfterPermissionGranted(PERMISSION_ID) + public void saveToFileByPermission() { + String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + if (EasyPermissions.hasPermissions(this, permissions)) { + if (mType == TYPE_SHARE) { + mFragment.share(); + } else { + mFragment.save(); + } + } else { + EasyPermissions.requestPermissions(this, "请授予文件读写权限", PERMISSION_ID, permissions); + } + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + DialogHelper.getConfirmDialog(this, "", "没有权限, 你需要去设置中开启读取手机存储权限.", "去设置", "取消", false, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + //finish(); + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //finish(); + } + }).show(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + protected abstract ShareFragment getShareFragment(); +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/share/ShareFragment.java b/app/src/main/java/net/oschina/app/improve/detail/share/ShareFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..77e4d16370ffc4fb9f070999744ab9ffac41a228 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/share/ShareFragment.java @@ -0,0 +1,138 @@ +package net.oschina.app.improve.detail.share; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.net.Uri; +import android.os.Environment; +import android.support.v4.widget.NestedScrollView; +import android.view.View; + +import net.oschina.app.R; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.dialog.ShareDialog; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.OWebView; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.common.utils.StreamUtil; + +import java.io.File; +import java.io.FileOutputStream; + +/** + * 文章详情分享界面 + * Created by huanghaibin on 2017/9/25. + */ + +public abstract class ShareFragment extends BaseFragment implements Runnable { + protected OWebView mWebView; + protected SubBean mBean; + private ShareDialog mShareDialog; + private Bitmap mBitmap; + private ProgressDialog mDialog; + protected NestedScrollView mViewScroller; + + @Override + protected int getLayoutId() { + return R.layout.fragment_share; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mWebView = (OWebView) mRoot.findViewById(R.id.webView); + mWebView.setUseShareCss(true); + mBean = (SubBean) getArguments().getSerializable("bean"); + mViewScroller = (NestedScrollView) mRoot.findViewById(R.id.lay_nsv); + mDialog = DialogHelper.getProgressDialog(mContext); + mDialog.setMessage("请稍候..."); + } + + @Override + protected void initData() { + super.initData(); + mWebView.loadDetailDataAsync(mBean.getBody(), (Runnable) mContext); + mShareDialog = new ShareDialog(getActivity(), -1, false); + } + + public void share() { + recycle(); + mDialog.show(); + mRoot.postDelayed(this, 2000); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + public void save() { + recycle(); + mBitmap = create(mViewScroller.getChildAt(0)); + FileOutputStream os = null; + try { + String url = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + .getAbsolutePath() + File.separator + "开源中国/save/"; + File file = new File(url); + if (!file.exists()) { + file.mkdirs(); + } + String path = String.format("%s%s.jpg", url, System.currentTimeMillis()); + os = new FileOutputStream(path); + mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + os.flush(); + os.close(); + SimplexToast.show(mContext, "保存成功"); + Uri localUri = Uri.fromFile(new File(path)); + Intent localIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, localUri); + getActivity().sendBroadcast(localIntent); + } catch (Exception e) { + e.printStackTrace(); + SimplexToast.show(mContext, "保存失败"); + } finally { + StreamUtil.close(os); + recycle(); + } + } + + @Override + public void run() { + mBitmap = create(mViewScroller.getChildAt(0)); + mShareDialog.bitmap(mBitmap); + mDialog.dismiss(); + mShareDialog.show(); + } + + @Override + public void onPause() { + super.onPause(); + mShareDialog.dismiss(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + recycle(); + } + + private void recycle() { + if (mBitmap != null && mBitmap.isRecycled()) { + mBitmap.recycle(); + } + } + + private static Bitmap create(View v) { + try { + int w = v.getWidth(); + int h = v.getHeight(); + Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565); + Canvas c = new Canvas(bmp); + c.drawColor(Color.WHITE); + v.layout(0, 0, w, h); + v.draw(c); + return bmp; + } catch (OutOfMemoryError error) { + error.printStackTrace(); + return null; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/share/blog/ShareBlogActivity.java b/app/src/main/java/net/oschina/app/improve/detail/share/blog/ShareBlogActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..e2b9ea75ce7aad33da2b8be8fcf49469c8cd0bb6 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/share/blog/ShareBlogActivity.java @@ -0,0 +1,75 @@ +package net.oschina.app.improve.detail.share.blog; + +import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; +import android.view.View; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.detail.share.ShareActivity; +import net.oschina.app.improve.detail.share.ShareFragment; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; + +import butterknife.Bind; + +/** + * 博客长图分享 + * Created by huanghaibin on 2017/9/25. + */ + +public class ShareBlogActivity extends ShareActivity { + + @Bind(R.id.iv_avatar) + PortraitView mImageAvatar; + + + @Bind(R.id.tv_name) + TextView mTextName; + + @Bind(R.id.tv_pub_date) + TextView mTextPubDate; + + @Bind(R.id.tv_title) + TextView mTextTitle; + + @Bind(R.id.tv_detail_abstract) + TextView mTextAbstract; + + + public static void show(Context context, SubBean bean) { + Intent intent = new Intent(context, ShareBlogActivity.class); + intent.putExtra("bean", bean); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_blog_share; + } + + @Override + protected void initData() { + super.initData(); + Author author = mBean.getAuthor(); + if (author != null) { + mTextName.setText(author.getName()); + mImageAvatar.setup(author); + } + mTextPubDate.setText(StringUtils.formatYearMonthDay(mBean.getPubDate())); + mTextTitle.setText(mBean.getTitle()); + mTextAbstract.setText(mBean.getSummary()); + if (TextUtils.isEmpty(mBean.getSummary())) { + findViewById(R.id.line).setVisibility(View.GONE); + findViewById(R.id.line1).setVisibility(View.GONE); + mTextAbstract.setVisibility(View.GONE); + } + } + @Override + protected ShareFragment getShareFragment() { + return ShareBlogFragment.newInstance(mBean); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/share/blog/ShareBlogFragment.java b/app/src/main/java/net/oschina/app/improve/detail/share/blog/ShareBlogFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..0ad82cfbd5eb13e0f57eea60632a85d7bcba6078 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/share/blog/ShareBlogFragment.java @@ -0,0 +1,74 @@ +package net.oschina.app.improve.detail.share.blog; + +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.detail.share.ShareFragment; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; + +import butterknife.Bind; + +/** + * 博客长图分享 + * Created by huanghaibin on 2017/9/25. + */ + +public class ShareBlogFragment extends ShareFragment { + + @Bind(R.id.iv_avatar) + PortraitView mImageAvatar; + + + @Bind(R.id.tv_name) + TextView mTextName; + + @Bind(R.id.tv_pub_date) + TextView mTextPubDate; + + @Bind(R.id.tv_title) + TextView mTextTitle; + + @Bind(R.id.tv_detail_abstract) + TextView mTextAbstract; + + + static ShareFragment newInstance(SubBean bean) { + ShareBlogFragment fragment = new ShareBlogFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable("bean", bean); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_blog_share; + } + + + @Override + protected void initData() { + super.initData(); + Author author = mBean.getAuthor(); + if (author != null) { + mTextName.setText(author.getName()); + mImageAvatar.setup(author); + } + mTextPubDate.setText(StringUtils.formatYearMonthDay(mBean.getPubDate())); + mTextTitle.setText(mBean.getTitle()); + mTextAbstract.setText(mBean.getSummary()); + if (TextUtils.isEmpty(mBean.getSummary())) { + mRoot.findViewById(R.id.line).setVisibility(View.GONE); + mRoot.findViewById(R.id.line1).setVisibility(View.GONE); + mTextAbstract.setVisibility(View.GONE); + } + } + + +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/share/news/ShareNewsActivity.java b/app/src/main/java/net/oschina/app/improve/detail/share/news/ShareNewsActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..5c8709c29e1e28b7ea66a7ee6513617c131620a0 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/share/news/ShareNewsActivity.java @@ -0,0 +1,68 @@ +package net.oschina.app.improve.detail.share.news; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.detail.share.ShareActivity; +import net.oschina.app.improve.detail.share.ShareFragment; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; + +import butterknife.Bind; + +/** + * 资讯长图分享 + * Created by huanghaibin on 2017/9/25. + */ + +public class ShareNewsActivity extends ShareActivity { + + @Bind(R.id.tv_title) + TextView mTextTitle; + + @Bind(R.id.tv_pub_date) + TextView mTextPubDate; + + @Bind(R.id.tv_author) + TextView mTextAuthor; + + @Bind(R.id.iv_avatar) + PortraitView mPortraitView; + + + public static void show(Context context, SubBean bean) { + Intent intent = new Intent(context, ShareNewsActivity.class); + intent.putExtra("bean", bean); + context.startActivity(intent); + } + + + @Override + protected int getContentView() { + return R.layout.activity_news_share; + } + + + @SuppressLint("SetTextI18n") + @Override + protected void initData() { + super.initData(); + mTextTitle.setText(mBean.getTitle()); + mTextPubDate.setText("发布于 " + StringUtils.formatYearMonthDay(mBean.getPubDate())); + Author author = mBean.getAuthor(); + if (author != null) { + mTextAuthor.setText(author.getName()); + } + mPortraitView.setup(mBean.getAuthor()); + } + + @Override + protected ShareFragment getShareFragment() { + return ShareNewsFragment.newInstance(mBean); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/share/news/ShareNewsFragment.java b/app/src/main/java/net/oschina/app/improve/detail/share/news/ShareNewsFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..882cb72c8af53308e88f50d014703c3a93257941 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/share/news/ShareNewsFragment.java @@ -0,0 +1,61 @@ +package net.oschina.app.improve.detail.share.news; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.detail.share.ShareFragment; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; + +import butterknife.Bind; + +/** + * 资讯长图分享 + * Created by huanghaibin on 2017/9/25. + */ + +public class ShareNewsFragment extends ShareFragment { + + @Bind(R.id.tv_title) + TextView mTextTitle; + + @Bind(R.id.tv_pub_date) + TextView mTextPubDate; + + @Bind(R.id.tv_author) + TextView mTextAuthor; + + @Bind(R.id.iv_avatar) + PortraitView mPortraitView; + + static ShareFragment newInstance(SubBean bean) { + ShareNewsFragment fragment = new ShareNewsFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable("bean", bean); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_news_share; + } + + @SuppressLint("SetTextI18n") + @Override + protected void initData() { + super.initData(); + mTextTitle.setText(mBean.getTitle()); + mTextPubDate.setText("发布于 " + StringUtils.formatYearMonthDay(mBean.getPubDate())); + Author author = mBean.getAuthor(); + if (author != null) { + mTextAuthor.setText(author.getName()); + } + mPortraitView.setup(mBean.getAuthor()); + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/sign/SignUpActivity.java b/app/src/main/java/net/oschina/app/improve/detail/sign/SignUpActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..5fb4fd6af3465995a356401ff6dc1247417efdcb --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/sign/SignUpActivity.java @@ -0,0 +1,91 @@ +package net.oschina.app.improve.detail.sign; + +import android.app.Activity; +import android.content.Intent; +import android.support.v4.app.Fragment; +import android.view.View; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.activities.BaseBackActivity; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.ui.empty.EmptyLayout; + +import butterknife.Bind; + +/** + * 新版活动报名页面,动态请求活动报名参数,按服务器返回的顺序inflate各自对应的View + * Created by haibin + * on 2016/12/5. + */ + +public class SignUpActivity extends BackActivity implements SignUpContract.EmptyView { + + @Bind(R.id.error_layout) + EmptyLayout mEmptyLayout; + + private SignUpFragment mFragment; + + private SignUpPresenter mPresenter; + + private long mSourceId; + + public static void show(Fragment fragment, long sourceId) { + Intent intent = new Intent(fragment.getActivity(), SignUpActivity.class); + intent.putExtra("sourceId", sourceId); + fragment.startActivityForResult(intent, 0x01); + } + + public static void show(Activity activity, long sourceId) { + Intent intent = new Intent(activity, SignUpActivity.class); + intent.putExtra("sourceId", sourceId); + activity.startActivityForResult(intent, 0x01); + } + + @Override + protected int getContentView() { + return R.layout.activity_sign_up; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mSourceId = getIntent().getLongExtra("sourceId", 0); + mFragment = SignUpFragment.newInstance(mSourceId); + addFragment(R.id.fl_content, mFragment); + mEmptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + mEmptyLayout.setOnLayoutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mEmptyLayout.getErrorState() != EmptyLayout.NETWORK_LOADING) { + mEmptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + mPresenter.getSignUpOptions(mSourceId); + } + } + }); + } + + @Override + protected void initData() { + super.initData(); + + if (mSourceId <= 0) { + SimplexToast.show(this, "活动资源不存在"); + finish(); + } + mPresenter = new SignUpPresenter(mFragment, this); + mPresenter.getSignUpOptions(mSourceId); + } + + @Override + public void hideEmptyLayout() { + mEmptyLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + } + + @Override + public void showErrorLayout(int errorType) { + mEmptyLayout.setErrorType(errorType); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/sign/SignUpContract.java b/app/src/main/java/net/oschina/app/improve/detail/sign/SignUpContract.java new file mode 100644 index 0000000000000000000000000000000000000000..badc36945a453983b3646c5acefd9d98b54d65a6 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/sign/SignUpContract.java @@ -0,0 +1,43 @@ +package net.oschina.app.improve.detail.sign; + +import net.oschina.app.improve.base.BasePresenter; +import net.oschina.app.improve.base.BaseView; +import net.oschina.app.improve.bean.EventDetail; +import net.oschina.app.improve.bean.SignUpEventOptions; + +import java.util.List; + +/** + * Created by haibin + * on 2016/12/5. + */ +interface SignUpContract { + + interface EmptyView { + void hideEmptyLayout(); + + void showErrorLayout(int errorType); + } + + interface View extends BaseView { + void showGetSignUpOptionsSuccess(List options); + + void showSignUpSuccess(EventDetail detail); + + void showSignUpError(String message); + + void showInputEmpty(String message); + } + + interface Presenter extends BasePresenter { + /** + * 获得报名参数 + */ + void getSignUpOptions(long sourceId); + + /** + * 报名 + */ + void signUpEvent(long sourceId); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/sign/SignUpFragment.java b/app/src/main/java/net/oschina/app/improve/detail/sign/SignUpFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..34f978814aacf20bcfe24b71a4e87f53eaed070a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/sign/SignUpFragment.java @@ -0,0 +1,119 @@ +package net.oschina.app.improve.detail.sign; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.LinearLayout; + +import net.oschina.app.R; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.bean.EventDetail; +import net.oschina.app.improve.bean.SignUpEventOptions; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.SimplexToast; + +import java.util.List; + +/** + * Created by haibin + * on 2016/12/5. + */ + +public class SignUpFragment extends BaseFragment implements SignUpContract.View { + private SignUpContract.Presenter mPresenter; + private LinearLayout mLayoutRoot; + private long mSourceId; + private ProgressDialog mDialog; + + public static SignUpFragment newInstance(long sourceId) { + SignUpFragment fragment = new SignUpFragment(); + Bundle bundle = new Bundle(); + bundle.putLong("sourceId", sourceId); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_sign_up; + } + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + mSourceId = bundle.getLong("sourceId", 0); + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mLayoutRoot = (LinearLayout) mRoot.findViewById(R.id.ll_root); + mDialog = DialogHelper.getProgressDialog(mContext); + } + + @Override + public void showGetSignUpOptionsSuccess(final List options) { + mLayoutRoot.removeAllViews(); + for (SignUpEventOptions option : options) { + View view = ViewFactory.createView(getActivity(), mInflater, option); + if (view != null) + mLayoutRoot.addView(view); + } + @SuppressLint("InflateParams") View view = mInflater.inflate(R.layout.event_sign_up_button, null); + view.findViewById(R.id.btn_sign_up).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + for (SignUpEventOptions option : options) { + if(TextUtils.isEmpty(option.getValue()) && option.isRequired()){ + SimplexToast.show(mContext,"请完善报名信息"); + return; + } + } + mPresenter.signUpEvent(mSourceId); + mDialog.setMessage("正在提交报名..."); + mDialog.show(); + } + }); + mLayoutRoot.addView(view); + } + + @Override + public void showSignUpSuccess(EventDetail detail) { + SimplexToast.show(mContext, "报名成功"); + Intent intent = new Intent(); + getActivity().setResult(Activity.RESULT_OK, intent); + mDialog.dismiss(); + getActivity().finish(); + } + + @Override + public void showSignUpError(String message) { + SimplexToast.show(mContext, message); + mDialog.dismiss(); + } + + @Override + public void showInputEmpty(String message) { + SimplexToast.show(mContext, message); + } + + @Override + public void setPresenter(SignUpContract.Presenter presenter) { + this.mPresenter = presenter; + } + + @Override + public void showNetworkError(int strId) { + SimplexToast.show(mContext, strId); + } + + @Override + public void onDestroy() { + mLayoutRoot.removeAllViews(); + super.onDestroy(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/sign/SignUpPresenter.java b/app/src/main/java/net/oschina/app/improve/detail/sign/SignUpPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..863f7a53254ad6b5c42e1eb482c9f24da8d05600 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/sign/SignUpPresenter.java @@ -0,0 +1,129 @@ +package net.oschina.app.improve.detail.sign; + +import android.text.TextUtils; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.EventDetail; +import net.oschina.app.improve.bean.SignUpEventOptions; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.ui.empty.EmptyLayout; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2016/12/5. + */ + +class SignUpPresenter implements SignUpContract.Presenter { + private final SignUpContract.View mView; + private List mOptions; + private final SignUpContract.EmptyView mEmptyView; + + SignUpPresenter(SignUpContract.View mView, SignUpContract.EmptyView mEmptyView) { + this.mView = mView; + this.mEmptyView = mEmptyView; + this.mView.setPresenter(this); + } + + @Override + public void getSignUpOptions(long sourceId) { + OSChinaApi.getSignUpOptions(sourceId, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mEmptyView.showErrorLayout(EmptyLayout.NETWORK_ERROR); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + mOptions = resultBean.getResult(); + mView.showGetSignUpOptionsSuccess(resultBean.getResult()); + mEmptyView.hideEmptyLayout(); + } else { + mEmptyView.showErrorLayout(EmptyLayout.NODATA); + } + } catch (Exception e) { + e.printStackTrace(); + mEmptyView.showErrorLayout(EmptyLayout.NODATA); + } + } + }); + } + + @Override + public void signUpEvent(long sourceId) { + if (!check()) + return; + + OSChinaApi.signUpEvent(sourceId, getOptions(), new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.tip_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + mView.showSignUpSuccess(resultBean.getResult()); + } else { + mView.showSignUpError(resultBean.getMessage()); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showSignUpError("报名失败"); + } + } + }); + } + + private boolean check() { + for (SignUpEventOptions options : mOptions) { + if (options.isRequired() && options.getSelectList() != null) { + if (options.getSelectList().size() == 0) { + mView.showInputEmpty(options.getLabel() + "不能为空"); + return false; + } + } + if (options.isRequired() && options.getValue() == null && TextUtils.isEmpty(options.getDefaultValue()) && options.getFormType() != SignUpEventOptions.FORM_TYPE_CHECK_BOX) { + mView.showInputEmpty(options.getLabel() + "不能为空"); + return false; + } + } + return true; + } + + private List getOptions() { + List ops = new ArrayList<>(); + for (SignUpEventOptions options : mOptions) { + if (options.getSelectList() == null) { + ops.add(options); + } else { + for (String s : options.getSelectList()) { + SignUpEventOptions sign = new SignUpEventOptions(); + sign.setKey(options.getKey()); + sign.setValue(s); + ops.add(sign); + } + } + } + return ops; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/sign/StringAdapter.java b/app/src/main/java/net/oschina/app/improve/detail/sign/StringAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..70afcb5c1406e48d8f1a06461bc544fb3ded15f2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/sign/StringAdapter.java @@ -0,0 +1,63 @@ +package net.oschina.app.improve.detail.sign; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; + +/** + * Created by haibin + * on 2016/12/7. + */ + +class StringAdapter extends BaseRecyclerAdapter { + StringAdapter(Context context) { + super(context, NEITHER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new SelectViewHolder(mInflater.inflate(R.layout.item_list_select, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Select item, int position) { + SelectViewHolder h = (SelectViewHolder) holder; + h.mTextSelect.setText(item.getLabel()); + h.mTextSelect.setTextColor(item.isEnable() ? 0xff111111 : 0xff9A9A9A); + } + + private static class SelectViewHolder extends RecyclerView.ViewHolder { + TextView mTextSelect; + + SelectViewHolder(View itemView) { + super(itemView); + mTextSelect = (TextView) itemView.findViewById(R.id.tv_select); + } + } + + public static class Select { + private boolean enable; + private String label; + + public boolean isEnable() { + return enable; + } + + public void setEnable(boolean enable) { + this.enable = enable; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/sign/StringParams.java b/app/src/main/java/net/oschina/app/improve/detail/sign/StringParams.java new file mode 100644 index 0000000000000000000000000000000000000000..bc69a1aaf84a69e7910b52361db33866e7795fe7 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/sign/StringParams.java @@ -0,0 +1,61 @@ +package net.oschina.app.improve.detail.sign; + +import com.loopj.android.http.RequestParams; +import com.loopj.android.http.ResponseHandlerInterface; + +import java.io.IOException; +import java.util.IdentityHashMap; +import java.util.LinkedList; +import java.util.List; + +import cz.msebera.android.httpclient.HttpEntity; +import cz.msebera.android.httpclient.client.entity.UrlEncodedFormEntity; +import cz.msebera.android.httpclient.message.BasicNameValuePair; + +/** + * Created by haibin + * on 2016/12/8. + */ + +public class StringParams extends RequestParams { + private IdentityHashMap mParams = new IdentityHashMap<>(); + + public void putForm(String key, String value) { + mParams.put(new Param(key), value); + } + + @Override + public HttpEntity getEntity(ResponseHandlerInterface progressHandler) throws IOException { + return new UrlEncodedFormEntity(getParamsList(), contentEncoding); + } + + protected List getParamsList() { + List lparams = new LinkedList<>(); + + for (IdentityHashMap.Entry entry : mParams.entrySet()) { + lparams.add(new BasicNameValuePair(entry.getKey().name, entry.getValue())); + } + return lparams; + } + + @Override + public String toString() { + return super.toString(); + } + + private static class Param { + private String name; + + Param(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/sign/ViewFactory.java b/app/src/main/java/net/oschina/app/improve/detail/sign/ViewFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..7701ff7001a41d025e47799a3911fd194d864d5b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/sign/ViewFactory.java @@ -0,0 +1,283 @@ +package net.oschina.app.improve.detail.sign; + +import android.app.Activity; +import android.content.DialogInterface; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.Editable; +import android.text.InputType; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.SignUpEventOptions; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.common.widget.FlowLayout; + +import java.util.ArrayList; + +/** + * Created by haibin + * on 2016/12/7. + */ + +final class ViewFactory { + static View createView(Activity activity, LayoutInflater inflater, final SignUpEventOptions options) { + View view = null; + switch (options.getFormType()) { + case SignUpEventOptions.FORM_TYPE_TEXT: + view = getEditTextView(activity, inflater, options, InputType.TYPE_CLASS_TEXT); + break; + case SignUpEventOptions.FORM_TYPE_TEXT_AREA: + //view = getEditTextArea(activity, inflater, options); + break; + case SignUpEventOptions.FORM_TYPE_SELECT: + view = getSelect(activity, inflater, options); + break; + case SignUpEventOptions.FORM_TYPE_CHECK_BOX: + view = getCheckBox(activity, inflater, options); + break; + case SignUpEventOptions.FORM_TYPE_RADIO: + view = getRadios(activity, inflater, options); + break; + case SignUpEventOptions.FORM_TYPE_EMAIL: + view = getEditTextView(activity, inflater, options, InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + break; + case SignUpEventOptions.FORM_TYPE_DATE: + break; + case SignUpEventOptions.FORM_TYPE_MOBILE: + view = getEditTextView(activity, inflater, options, InputType.TYPE_CLASS_PHONE); + break; + case SignUpEventOptions.FORM_TYPE_NUMBER: + view = getEditTextView(activity, inflater, options, InputType.TYPE_CLASS_NUMBER); + break; + case SignUpEventOptions.FORM_TYPE_URL: + view = getEditTextView(activity, inflater, options, InputType.TYPE_TEXT_VARIATION_URI); + break; + default: + break; + } + return view; + } + + /** + * 单行输入 + */ + private static View getEditTextView(Activity activity, LayoutInflater inflater, final SignUpEventOptions options, int inputType) { + View view = inflater.inflate(R.layout.event_sign_up_edit_text, null); + ((TextView) view.findViewById(R.id.tv_label)).setText(options.getLabel() + (options.isRequired() ? "" : "(选填)") + ":"); + EditText editText = (EditText) view.findViewById(R.id.et_value); + editText.setText(options.getDefaultValue()); + options.setValue(options.getDefaultValue()); + editText.setInputType(inputType); + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + options.setValue(s.toString()); + } + }); + return view; + } + + /** + * 多行输入的V + */ + private static View getEditTextArea(Activity activity, LayoutInflater inflater, final SignUpEventOptions options) { + View view = inflater.inflate(R.layout.event_sign_up_edit_text_area, null); + ((TextView) view.findViewById(R.id.tv_area)).setText(options.getLabel() + (options.isRequired() ? "" : "(选填)") + ":"); + EditText editText = (EditText) view.findViewById(R.id.et_area); + editText.setText(options.getDefaultValue()); + options.setValue(options.getDefaultValue()); + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + options.setValue(s.toString()); + } + }); + return view; + } + + + private static View getRadios(Activity activity, LayoutInflater inflater, final SignUpEventOptions options) { + View view = inflater.inflate(R.layout.event_sign_up_radios, null); + ((TextView) view.findViewById(R.id.tv_label)).setText(options.getLabel() + (options.isRequired() ? "" : "(选填)") + ":"); + RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.rg_options); + RadioGroup.LayoutParams params = new RadioGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMarginEnd(100); + if (!TextUtils.isEmpty(options.getOption())) { + String[] list = options.getOption().split(";"); + String[] status = null; + if (!TextUtils.isEmpty(options.getOptionStatus())) + status = options.getOptionStatus().split(";"); + for (int i = 0; i < list.length; i++) { + RadioButton button = new RadioButton(activity); + button.setLayoutParams(params); + button.setText(list[i]); + if (!TextUtils.isEmpty(options.getDefaultValue())) { + button.setChecked(list[0].equals(options.getDefaultValue())); + options.setValue(options.getDefaultValue()); + } else { + button.setChecked(i == 0); + options.setValue(list[0]); + } + boolean enable; + if (status == null) + enable = true; + else if (status.length <= i) + enable = true; + else + enable = "0".equals(status[0]); + button.setId(i); + button.setEnabled(enable); + radioGroup.addView(button); + } + } + radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + String s[] = options.getOption().split(";"); + if (s != null) + options.setValue(s[checkedId]); + } + }); + return view; + } + + private static View getCheckBox(Activity activity, LayoutInflater inflater, final SignUpEventOptions options) { + View view = inflater.inflate(R.layout.event_sign_up_check_box, null); + FlowLayout ll_check_box = (FlowLayout) view.findViewById(R.id.fl_check_box); + ((TextView) view.findViewById(R.id.tv_label)).setText(options.getLabel() + (options.isRequired() ? "" : "(选填)") + ":"); + RadioGroup.LayoutParams params = new RadioGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMarginEnd(100); + if (options.getSelectList() == null) + options.setSelectList(new ArrayList()); + if (!TextUtils.isEmpty(options.getOption())) { + final String[] list = options.getOption().split(";"); + String[] status = null; + if (!TextUtils.isEmpty(options.getOptionStatus())) + status = options.getOptionStatus().split(";"); + for (int i = 0; i < list.length; i++) { + final CheckBox button = new CheckBox(activity); + button.setLayoutParams(params); + button.setText(list[i]); + boolean enable; + if (status == null) + enable = true; + else if (status.length <= i) + enable = true; + else + enable = "0".equals(status[i]); + button.setId(i); + button.setEnabled(enable); + button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + String item = list[button.getId()]; + if (isChecked) { + options.getSelectList().add(item); + } else { + options.getSelectList().remove(item); + } + } + }); + ll_check_box.addView(button); + } + } + return view; + } + + private static View getSelect(Activity activity, LayoutInflater inflater, final SignUpEventOptions options) { + View view = inflater.inflate(R.layout.event_sign_up_select, null); + ((TextView) view.findViewById(R.id.tv_label)).setText(options.getLabel() + (options.isRequired() ? "" : "(选填)") + ":"); + final TextView tv_select = (TextView) view.findViewById(R.id.tv_select); + tv_select.setText(options.getDefaultValue()); + RecyclerView rv_select = (RecyclerView) inflater.inflate(R.layout.event_sign_up_select_list, null); + final StringAdapter adapter = new StringAdapter(activity); + rv_select.setLayoutManager(new LinearLayoutManager(activity)); + rv_select.setAdapter(adapter); + if (options.getOption() != null) { + String[] status = null; + if (!TextUtils.isEmpty(options.getOptionStatus())) + status = options.getOptionStatus().split(";"); + String[] list = options.getOption().split(";"); + for (int i = 0; i < list.length; i++) { + StringAdapter.Select s = new StringAdapter.Select(); + s.setLabel(list[i]); + boolean enable = false; + if (status == null) + enable = true; + else if (status.length <= i) + enable = true; + else + enable = "0".equals(status[i]); + s.setEnable(enable); + adapter.addItem(s); + } + } + + + final AlertDialog dialog = DialogHelper.getSelectDialog(activity, rv_select, "取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }).create(); + + adapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + StringAdapter.Select s = adapter.getItem(position); + if (s.isEnable()) { + tv_select.setText(s.getLabel()); + options.setValue(s.getLabel()); + dialog.dismiss(); + } + } + }); + + final LinearLayout linearLayout = (LinearLayout) view.findViewById(R.id.ll_select); + linearLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dialog.show(); + } + }); + return view; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/v2/CommentView.java b/app/src/main/java/net/oschina/app/improve/detail/v2/CommentView.java new file mode 100644 index 0000000000000000000000000000000000000000..081b3bd5f816bd51d4346f9e6efb399636d02c70 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/v2/CommentView.java @@ -0,0 +1,364 @@ +package net.oschina.app.improve.detail.v2; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.bumptech.glide.RequestManager; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.comment.Refer; +import net.oschina.app.improve.bean.comment.Vote; +import net.oschina.app.improve.comment.CommentsActivity; +import net.oschina.app.improve.comment.CommentsUtil; +import net.oschina.app.improve.comment.QuesAnswerDetailActivity; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; +import net.oschina.app.widget.TweetTextView; +import net.oschina.common.utils.CollectionUtil; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by fei + * on 2016/11/16. + * desc: 资讯、问答、博客、翻译、活动、软件详情评论列表当中进行展示的子view. + * 包括直接渲染出评论下的refer和reply + */ +public class CommentView extends FrameLayout implements View.OnClickListener { + + private long mId; + private int mType; + private TextView mTitle; + private String mShareTitle; + private TextView mSeeMore; + private LinearLayout mLayComments; + private LinearLayout mLinearComment, mLinearTip; + + private OnCommentClickListener mListener; + + public CommentView(Context context) { + super(context); + init(); + } + + public CommentView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public CommentView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + + LayoutInflater inflater = LayoutInflater.from(getContext()); + inflater.inflate(R.layout.detail_comment_view, this, true); + mTitle = (TextView) findViewById(R.id.tv_detail_comment); + mLinearComment = (LinearLayout) findViewById(R.id.ll_comment); + mLayComments = (LinearLayout) findViewById(R.id.lay_detail_comment); + mLinearTip = (LinearLayout) findViewById(R.id.ll_tip); + mSeeMore = (TextView) findViewById(R.id.tv_see_more_comment); + mLinearTip.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mListener != null) { + mListener.onShowComment(v); + } + } + }); + } + + public void setTitle(String title) { + if (!TextUtils.isEmpty(title)) { + mTitle.setText(title); + } + } + + public void setShareTitle(String shareTitle) { + this.mShareTitle = shareTitle; + } + + + /** + * @return TypeToken + */ + Type getCommentType() { + return new TypeToken>>() { + }.getType(); + } + + /** + * @return TypeToken + */ + Type getVoteType() { + return new TypeToken>() { + }.getType(); + } + + public void init(long id, final int type, int order, final int commentCount, final RequestManager imageLoader, + final OnCommentClickListener onCommentClickListener) { + this.mId = id; + this.mType = type; + + mSeeMore.setVisibility(View.GONE); + mSeeMore.setText(String.format("查看所有 %s 条评论", commentCount)); + mLinearComment.setVisibility(GONE); + mLinearTip.setVisibility(GONE); + this.mListener = onCommentClickListener; + OSChinaApi.getComments(id, type, "refer,reply", order, null, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mLinearComment.setVisibility(View.GONE); + mLinearTip.setVisibility(View.VISIBLE); + } + + @SuppressLint("DefaultLocale") + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + + ResultBean> resultBean = AppOperator.createGson().fromJson(responseString, getCommentType()); + if (resultBean.isSuccess()) { + + List comments = resultBean.getResult().getItems(); + setTitle(String.format("%s", type == News.TYPE_QUESTION ? "热门回复" : "热门评论")); + mSeeMore.setVisibility(VISIBLE); + mSeeMore.setOnClickListener(CommentView.this); + Comment[] array = CollectionUtil.toArray(comments, Comment.class); + initComment(array, imageLoader, onCommentClickListener); + } else { + mLinearComment.setVisibility(View.GONE); + mLinearTip.setVisibility(View.VISIBLE); + } + + } catch (Exception e) { + onFailure(statusCode, headers, responseString, e); + } + } + }); + } + + private void initComment(final Comment[] comments, final RequestManager imageLoader, final OnCommentClickListener onCommentClickListener) { + + if (mLayComments != null) + mLayComments.removeAllViews(); + if (comments != null && comments.length > 0) { + mLinearComment.setVisibility(VISIBLE); + mLayComments.setVisibility(VISIBLE); + for (int i = 0, len = comments.length; i < len; i++) { + final Comment comment = comments[i]; + if (comment != null) { + final ViewGroup lay = insertComment(true, comment, imageLoader, onCommentClickListener); + lay.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mType == OSChinaApi.COMMENT_EVENT || mType == OSChinaApi.COMMENT_QUESTION) { + QuesAnswerDetailActivity.show(lay.getContext(), comment, mId, mType); + } else { + onCommentClickListener.onClick(v, comment); + } + } + }); + + mLayComments.addView(lay); + if (i == len - 1) { + lay.findViewById(R.id.line).setVisibility(GONE); + } else { + lay.findViewById(R.id.line).setVisibility(View.VISIBLE); + } + } + } + } else { + mLinearComment.setVisibility(View.GONE); + mLinearTip.setVisibility(View.VISIBLE); + } + } + + + @SuppressLint("DefaultLocale") + private ViewGroup insertComment(final boolean first, final Comment comment, final RequestManager imageLoader, + final OnCommentClickListener onCommentClickListener) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + @SuppressLint("InflateParams") ViewGroup lay = (ViewGroup) inflater.inflate(R.layout.lay_comment_item, null, false); + + IdentityView identityView = (IdentityView) lay.findViewById(R.id.identityView); + PortraitView ivAvatar = (PortraitView) lay.findViewById(R.id.iv_avatar); + identityView.setup(comment.getAuthor()); + ivAvatar.setup(comment.getAuthor()); + ivAvatar.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + OtherUserHomeActivity.show(getContext(), comment.getAuthor().getId()); + } + }); + final ImageView ivComment = (ImageView) lay.findViewById(R.id.iv_best_answer); + final TextView tvVoteCount = (TextView) lay.findViewById(R.id.tv_vote_count); + tvVoteCount.setText(String.valueOf(comment.getVote())); + final ImageView ivVoteStatus = (ImageView) lay.findViewById(R.id.btn_vote); + +// commentBar.getBottomSheet().show(String.format("%s %s", +// ivComment.getResources().getString(R.string.reply_hint), comment.getAuthor().getName())); + + if (mType == OSChinaApi.COMMENT_QUESTION || mType == OSChinaApi.COMMENT_EVENT + || mType == OSChinaApi.COMMENT_TRANSLATION || mType == OSChinaApi.COMMENT_BLOG) { + + tvVoteCount.setVisibility(View.GONE); + ivVoteStatus.setVisibility(View.GONE); + if (comment.isBest()) { + ivComment.setEnabled(false); + ivComment.setImageResource(R.mipmap.label_best_answer); + ivComment.setVisibility(VISIBLE); + } + } else { + ivComment.setEnabled(true); + tvVoteCount.setText(String.valueOf(comment.getVote())); + tvVoteCount.setVisibility(View.VISIBLE); + ivVoteStatus.setVisibility(View.VISIBLE); + + if (comment.getVoteState() == 1) { + ivVoteStatus.setImageResource(R.mipmap.ic_thumbup_actived); + ivVoteStatus.setTag(true); + } else if (comment.getVoteState() == 0) { + ivVoteStatus.setImageResource(R.mipmap.ic_thumb_normal); + ivVoteStatus.setTag(null); + } + + ivVoteStatus.setOnClickListener(new OnClickListener() { + @Override + public void onClick(final View v) { + handVote(); + } + + private void handVote() { + if (ivVoteStatus.getTag() != null || comment.getVoteState() == 1) { + return; + } + if (!AccountHelper.isLogin()) { + LoginActivity.show(getContext()); + return; + } + if (!TDevice.hasInternet()) { + AppContext.showToast(getResources().getString(R.string.state_network_error), Toast.LENGTH_SHORT); + return; + } + + OSChinaApi.voteComment(mType, comment.getId(), comment.getAuthor().getId(), 1, new TextHttpResponseHandler() { + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + requestFailureHint(throwable); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, getVoteType()); + if (resultBean.isSuccess()) { + Vote vote = resultBean.getResult(); + if (vote != null) { + if (vote.getVoteState() == 1) { + comment.setVoteState(1); + ivVoteStatus.setTag(true); + ivVoteStatus.setImageResource(R.mipmap.ic_thumbup_actived); + } else if (vote.getVoteState() == 0) { + comment.setVoteState(0); + ivVoteStatus.setTag(null); + ivVoteStatus.setImageResource(R.mipmap.ic_thumb_normal); + } + long voteVoteCount = vote.getVote(); + comment.setVote(voteVoteCount); + tvVoteCount.setText(String.valueOf(voteVoteCount)); + } + AppContext.showToastShort("操作成功!!!"); + } else { + AppContext.showToastShort(resultBean.getMessage()); + } + } + + }); + } + }); + } + + String name = comment.getAuthor().getName(); + if (TextUtils.isEmpty(name)) { + name = getResources().getString(R.string.martian_hint); + } + + ((TextView) lay.findViewById(R.id.tv_name)).setText(name); + + ((TextView) lay.findViewById(R.id.tv_pub_date)).setText( + String.format("%s", StringUtils.formatSomeAgo(comment.getPubDate()))); + + TweetTextView content = ((TweetTextView) lay.findViewById(R.id.tv_content)); + CommentsUtil.formatHtml(getResources(), content, comment.getContent()); + Refer[] refers = comment.getRefer(); + + if (refers != null && refers.length > 0) { + View view = CommentsUtil.getReferLayout(inflater, refers, 0); + lay.addView(view, lay.indexOfChild(content)); + } + + if (!first) { + addView(lay, 0); + } + + return lay; + } + + @Override + public void onClick(View v) { + if (mId != 0 && mType != 0) + CommentsActivity.show((AppCompatActivity) getContext(), mId, mType, OSChinaApi.COMMENT_NEW_ORDER, mShareTitle); + } + + /** + * request network error + * + * @param throwable throwable + */ + protected void requestFailureHint(Throwable throwable) { + AppContext.showToastShort(R.string.request_error_hint); + if (throwable != null) { + throwable.printStackTrace(); + } + } + + public interface OnCommentClickListener { + void onClick(View view, Comment comment); + + void onShowComment(View view); + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/detail/v2/DetailActivity.java b/app/src/main/java/net/oschina/app/improve/detail/v2/DetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..f6804263cb4e6242e71197104f8df439a3114e84 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/v2/DetailActivity.java @@ -0,0 +1,677 @@ +package net.oschina.app.improve.detail.v2; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Handler; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.widget.LinearLayout; + +import net.oschina.app.AppConfig; +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.bean.Report; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.comment.CommentsActivity; +import net.oschina.app.improve.detail.db.Behavior; +import net.oschina.app.improve.detail.db.DBManager; +import net.oschina.app.improve.dialog.ShareDialog; +import net.oschina.app.improve.main.MainActivity; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.improve.tweet.service.TweetPublishService; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.utils.NetworkUtil; +import net.oschina.app.improve.widget.CommentShareView; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.improve.widget.adapter.OnKeyArrivedListenerAdapterV2; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * 新版本详情页实现 + * Created by haibin + * on 2016/11/30. + */ + +public abstract class DetailActivity extends BackActivity implements + DetailContract.EmptyView, Runnable, + CommentView.OnCommentClickListener, EasyPermissions.PermissionCallbacks { + + protected String mCommentHint; + protected DetailPresenter mPresenter; + protected EmptyLayout mEmptyLayout; + protected DetailFragment mDetailFragment; + protected ShareDialog mAlertDialog; + + protected long mStay;//该界面停留时间 + private long mStart; + protected Behavior mBehavior; + @SuppressWarnings("unused") + private boolean isInsert; + + protected CommentBar mDelegation; + + protected SubBean mBean; + protected String mIdent; + + protected long mCommentId; + protected long mCommentAuthorId; + protected boolean mInputDoubleEmpty = false; + + protected CommentShareView mShareView; + private AlertDialog mShareCommentDialog; + protected Comment mComment; + + @Override + protected int getContentView() { + return R.layout.activity_detail_v2; + } + + @SuppressLint("ClickableViewAccessibility") + @SuppressWarnings("all") + @Override + protected void initWidget() { + super.initWidget(); + setSwipeBackEnable(true); + DBManager.getInstance() + .create(Behavior.class); + DBManager.getInstance() + .alter(Behavior.class); + CommentShareView.clearShareImage(); + if (!TDevice.hasWebView(this)) { + finish(); + return; + } + if (TextUtils.isEmpty(mCommentHint)) + mCommentHint = getString(R.string.pub_comment_hint); + LinearLayout layComment = (LinearLayout) findViewById(R.id.ll_comment); + mShareView = (CommentShareView) findViewById(R.id.shareView); + mEmptyLayout = (EmptyLayout) findViewById(R.id.lay_error); + mEmptyLayout.setOnLayoutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mEmptyLayout.getErrorState() != EmptyLayout.NETWORK_LOADING) { + mEmptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + mPresenter.getDetail(); + } + } + }); + mBean = (SubBean) getIntent().getSerializableExtra("sub_bean"); + mIdent = getIntent().getStringExtra("ident"); + mDetailFragment = getDetailFragment(); + addFragment(R.id.lay_container, mDetailFragment); + mPresenter = new DetailPresenter(mDetailFragment, this, mBean, mIdent); + if (!mPresenter.isHideCommentBar()) { + mDelegation = CommentBar.delegation(this, layComment); + mDelegation.setCommentHint(mCommentHint); + mDelegation.getBottomSheet().getEditText().setHint(mCommentHint); + mDelegation.setFavDrawable(mBean.isFavorite() ? R.drawable.ic_faved : R.drawable.ic_fav); + + mDelegation.setFavListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!AccountHelper.isLogin()) { + LoginActivity.show(DetailActivity.this); + return; + } + mPresenter.favReverse(); + } + }); + + mDelegation.getBottomSheet().setMentionListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if ((AccountHelper.isLogin())) { + UserSelectFriendsActivity.show(DetailActivity.this, mDelegation.getBottomSheet().getEditText()); + } else { + LoginActivity.show(DetailActivity.this, 1); + } + } + }); + + mDelegation.setCommentCountListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CommentsActivity.show(DetailActivity.this, mBean.getId(), mBean.getType(), OSChinaApi.COMMENT_NEW_ORDER, mBean.getTitle()); + } + }); + + mDelegation.getBottomSheet().getEditText().setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL) { + handleKeyDel(); + } + return false; + } + }); + mDelegation.getBottomSheet().setCommitListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // showDialog("正在提交评论..."); + if (mDelegation == null) return; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(false); + mPresenter.addComment(mBean.getId(), + mBean.getType(), + mDelegation.getBottomSheet().getCommentText(), + 0, + mCommentId, + mCommentAuthorId); + } + }); + mDelegation.getBottomSheet().getEditText().setOnKeyArrivedListener(new OnKeyArrivedListenerAdapterV2(this)); + } + mEmptyLayout.post(new Runnable() { + @Override + public void run() { + mPresenter.getCache(); + mPresenter.getDetail(); + } + }); + if (mToolBar != null) + + mToolBar.setOnTouchListener(new OnDoubleTouchListener() { + @Override + void onMultiTouch(View v, MotionEvent event, int touchCount) { + if (touchCount == 2) { + mPresenter.scrollToTop(); + } + } + }); + + if (AccountHelper.isLogin() && + DBManager.getInstance() + .getCount(Behavior.class) >= 3) { + mPresenter.uploadBehaviors(DBManager.getInstance().get(Behavior.class)); + } + if (mShareView != null) { + mShareCommentDialog = DialogHelper.getRecyclerViewDialog(this, new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + switch (position) { + case 0: + TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(mComment.getContent())); + break; + case 1: + if (!AccountHelper.isLogin()) { + LoginActivity.show(DetailActivity.this, 1); + return; + } + if (mComment.getAuthor() == null || mComment.getAuthor().getId() == 0) { + SimplexToast.show(DetailActivity.this, "该用户不存在"); + return; + } + mCommentId = mComment.getId(); + mCommentAuthorId = mComment.getAuthor().getId(); + mDelegation.getCommentText().setHint(String.format("%s %s", getResources().getString(R.string.reply_hint), mComment.getAuthor().getName())); + mDelegation.getBottomSheet().show(String.format("%s %s", getResources().getString(R.string.reply_hint), mComment.getAuthor().getName())); + break; + case 2: + mShareView.init(mBean.getTitle(), mComment); + saveToFileByPermission(); + break; + } + mShareCommentDialog.dismiss(); + } + }).create(); + } + } + + private void initBehavior() { + if (AccountHelper.isLogin() && mBean.getType() != News.TYPE_EVENT && !isInsert) { + mBehavior = new Behavior(); + mBehavior.setUser(AccountHelper.getUserId()); + mBehavior.setUserName(AccountHelper.getUser().getName()); + mBehavior.setNetwork(NetworkUtil.getNetwork(this)); + mBehavior.setUrl(mBean.getHref()); + mBehavior.setOperateType(mBean.getType()); + mBehavior.setOperateTime(System.currentTimeMillis()); + mStart = mBehavior.getOperateTime(); + mBehavior.setOperation("read"); + mBehavior.setDevice(android.os.Build.MODEL); + mBehavior.setVersion(TDevice.getVersionName()); + mBehavior.setOs(android.os.Build.VERSION.RELEASE); + mBehavior.setKey(String.format("%s_%s_%s", "osc", mBean.getType(), mBean.getId())); + mBehavior.setUuid(OSCSharedPreference.getInstance().getDeviceUUID()); + // TODO: 2017/11/6 暂时取消收藏接口 +// isInsert = DBManager.getInstance() +// .insert(mBehavior); + } + } + + + @Override + public void hideEmptyLayout() { + mEmptyLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + } + + @Override + public void showErrorLayout(int errorType) { + mEmptyLayout.setErrorType(errorType); + } + + @Override + public void showGetDetailSuccess(SubBean bean) { + this.mBean = bean; + initBehavior(); + if (mDelegation != null) { + if (bean.getStatistics() != null) { + mDelegation.setCommentCount(bean.getStatistics().getComment()); + } + mDelegation.setFavDrawable(mBean.isFavorite() ? R.drawable.ic_faved : R.drawable.ic_fav); + } + if (mEmptyLayout != null) { + mEmptyLayout.postDelayed(new Runnable() { + @Override + public void run() { + if (mEmptyLayout != null) + mEmptyLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + } + }, 2000); + } + } + + @Override + public void run() { + hideEmptyLayout(); + mDetailFragment.onPageFinished(); + } + + + @Override + public void showFavReverseSuccess(boolean isFav, int favCount, int strId) { + if (mBehavior != null) { + mBehavior.setIsCollect(isFav ? 1 : 0); + DBManager.getInstance() + .update(mBehavior, "operate_time=?", String.valueOf(mBehavior.getOperateTime())); + } + if (mDelegation != null) { + mDelegation.setFavDrawable(isFav ? R.drawable.ic_faved : R.drawable.ic_fav); + } + } + + @Override + public void showCommentSuccess(Comment comment) { + //hideDialog(); + if (mBehavior != null) { + mBehavior.setIsComment(1); + DBManager.getInstance() + .update(mBehavior, "operate_time=?", String.valueOf(mBehavior.getOperateTime())); + } + if (mDelegation == null) + return; + if (mDelegation.getBottomSheet().isSyncToTweet()) { + TweetPublishService.startActionPublish(this, + mDelegation.getBottomSheet().getCommentText(), null, + About.buildShare(mBean.getId(), mBean.getType())); + } + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(true); + AppContext.showToastShort(R.string.pub_comment_success); + mDelegation.getCommentText().setHint(mCommentHint); + mDelegation.getBottomSheet().getEditText().setText(""); + mDelegation.getBottomSheet().getEditText().setHint(mCommentHint); + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommentCount(mBean.getStatistics().getComment()); + } + + @Override + public void showShareCommentView(Comment comment) { + if (mShareView == null) + return; + mShareView.init(mBean.getTitle(), comment); + } + + @Override + public void showCommentError(String message) { + //hideDialog(); + AppContext.showToastShort(R.string.pub_comment_failed); + mDelegation.setCommitButtonEnable(true); + } + + @Override + public void showUploadBehaviorsSuccess(int index, String time) { + DBManager.getInstance() + .delete(Behavior.class, "id<=?", String.valueOf(index)); + AppConfig.getAppConfig(this) + .set("upload_behavior_time", time); + } + + @SuppressLint("SetTextI18n") + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu_detail, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + if (mBean != null) { + if (mBean.getType() != News.TYPE_SOFTWARE) { + toShare(mBean.getTitle(), mBean.getBody(), mBean.getHref()); + } else { + Map extras = mBean.getExtra(); + if (extras != null) { + toShare(getExtraString(extras.get("softwareTitle")) + " " + getExtraString(extras.get("softwareName")), mBean.getBody(), mBean.getHref()); + } + } + } + break; + case R.id.menu_report: + if (!AccountHelper.isLogin()) { + LoginActivity.show(this); + return false; + } + toReport(mBean.getId(), mBean.getHref()); + break; + } + return false; + } + + protected void toReport(long id, String href) { + ReportDialog.create(this, id, href, Report.TYPE_BLOG, "").show(); + } + + @SuppressWarnings({"LoopStatementThatDoesntLoop", "SuspiciousMethodCalls"}) + protected void toShare(String title, String content, String url) { + if (TextUtils.isEmpty(title) || TextUtils.isEmpty(url) || mBean == null) + return; + if (content == null) + content = ""; + String imageUrl = null; + List images = mBean.getImages(); + switch (mBean.getType()) { + case News.TYPE_EVENT: + if (images != null && images.size() > 0) { + imageUrl = images.get(0).getHref(); + } + break; + case News.TYPE_SOFTWARE: + if (images != null && images.size() > 0) { + imageUrl = images.get(0).getThumb(); + if (imageUrl != null && imageUrl.contains("logo/default.png")) { + imageUrl = null; + } + break; + } + default: + String regex = "]+\\s?src=\"([^\"]+)\"\\s?[^>]*>"; + + Pattern pattern = Pattern.compile(regex); + + Matcher matcher = pattern.matcher(mBean.getBody()); + + while (matcher.find()) { + imageUrl = matcher.group(1); + break; + } + break; + } + content = content.trim(); + if (content.length() > 55) { + content = HTMLUtil.delHTMLTag(content); + if (content.length() > 55) + content = StringUtils.getSubString(0, 55, content); + } else { + content = HTMLUtil.delHTMLTag(content); + } + if (TextUtils.isEmpty(content)) + content = ""; + + // 分享 + if (mAlertDialog == null) { + mAlertDialog = new + ShareDialog(this, mBean.getId(), (mBean.getType() == News.TYPE_BLOG || mBean.getType() == News.TYPE_NEWS)) + .type(mBean.getType()) + .title(title) + .content(content) + .imageUrl(imageUrl)//如果没有图片,即url为null,直接加入app默认分享icon + .url(url).with(); + mAlertDialog.setBean(mBean); + } + mAlertDialog.show(); + if (mBehavior != null) { + mBehavior.setIsShare(1); + DBManager.getInstance() + .update(mBehavior, "operate_time=?", String.valueOf(mBehavior.getOperateTime())); + } + } + + @Override + public void onClick(View view, Comment comment) { + this.mComment = comment; + if (mShareCommentDialog != null) { + mShareCommentDialog.show(); + } + } + + @Override + public void onShowComment(View view) { + if (mDelegation != null) { + mDelegation.getBottomSheet().show(mCommentHint); + } + } + + protected void handleKeyDel() { + if (mCommentId != mBean.getId()) { + if (TextUtils.isEmpty(mDelegation.getBottomSheet().getCommentText())) { + if (mInputDoubleEmpty) { + mCommentId = mBean.getId(); + mCommentAuthorId = 0; + mDelegation.setCommentHint(mCommentHint); + mDelegation.getBottomSheet().getEditText().setHint(mCommentHint); + } else { + mInputDoubleEmpty = true; + } + } else { + mInputDoubleEmpty = false; + } + } + } + + @SuppressWarnings("all") + protected boolean getExtraBool(Object object) { + return object == null ? false : Boolean.valueOf(object.toString()); + } + + protected int getExtraInt(Object object) { + return object == null ? 0 : Double.valueOf(object.toString()).intValue(); + } + + @SuppressWarnings("all") + protected String getExtraString(Object object) { + return object == null ? "" : object.toString(); + } + + + protected abstract DetailFragment getDetailFragment(); + + @Override + public void finish() { + if (mEmptyLayout != null && mEmptyLayout.getErrorState() == EmptyLayout.HIDE_LAYOUT) + DetailCache.addCache(mBean); + super.finish(); + } + + @Override + protected void onResume() { + super.onResume(); + if (mShareCommentDialog != null) + mShareCommentDialog.dismiss(); + if (mStart != 0) + mStart = System.currentTimeMillis(); + if (mAlertDialog == null) + return; + mAlertDialog.hideProgressDialog(); + } + + @Override + protected void onPause() { + super.onPause(); + if (mShareCommentDialog != null) { + mShareCommentDialog.dismiss(); + } + } + + @Override + protected void onStop() { + super.onStop(); + mStay += (System.currentTimeMillis() - mStart) / 1000; + if (mBehavior != null) { + mBehavior.setStay(mStay); + DBManager.getInstance() + .update(mBehavior, "operate_time=?", String.valueOf(mBehavior.getOperateTime())); + } + } + + private static final int PERMISSION_ID = 0x0001; + + @SuppressWarnings("unused") + @AfterPermissionGranted(PERMISSION_ID) + public void saveToFileByPermission() { + String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + if (EasyPermissions.hasPermissions(this, permissions)) { + mShareView.share(); + } else { + EasyPermissions.requestPermissions(this, "请授予文件读写权限", PERMISSION_ID, permissions); + } + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + DialogHelper.getConfirmDialog(this, "", "没有权限, 你需要去设置中开启读取手机存储权限.", "去设置", "取消", false, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + //finish(); + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //finish(); + } + }).show(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == 1 && resultCode == RESULT_OK && mBehavior != null) { + mBehavior.setIsComment(1); + DBManager.getInstance() + .update(mBehavior, "operate_time=?", String.valueOf(mBehavior.getOperateTime())); + } + } + + private abstract class OnDoubleTouchListener implements View.OnTouchListener { + private long lastTouchTime = 0; + private AtomicInteger touchCount = new AtomicInteger(0); + private Runnable mRun = null; + private Handler mHandler; + + OnDoubleTouchListener() { + mHandler = new Handler(getMainLooper()); + } + + void removeCallback() { + if (mRun != null) { + mHandler.removeCallbacks(mRun); + mRun = null; + } + } + + @Override + public boolean onTouch(final View v, final MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + final long now = System.currentTimeMillis(); + lastTouchTime = now; + + touchCount.incrementAndGet(); + removeCallback(); + + mRun = new Runnable() { + @Override + public void run() { + if (now == lastTouchTime) { + onMultiTouch(v, event, touchCount.get()); + touchCount.set(0); + } + } + }; + + mHandler.postDelayed(mRun, getMultiTouchInterval()); + } + return true; + } + + + int getMultiTouchInterval() { + return 400; + } + + + abstract void onMultiTouch(View v, MotionEvent event, int touchCount); + } + + @Override + public void onBackPressed() { + if(!MainActivity.IS_SHOW){ + MainActivity.show(this); + } + super.onBackPressed(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if(!MainActivity.IS_SHOW){ + MainActivity.show(this); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/v2/DetailCache.java b/app/src/main/java/net/oschina/app/improve/detail/v2/DetailCache.java new file mode 100644 index 0000000000000000000000000000000000000000..937743b5e42e35db491827bdfabb2676c4b8d369 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/v2/DetailCache.java @@ -0,0 +1,106 @@ +package net.oschina.app.improve.detail.v2; + +import android.content.Context; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.util.TLog; +import net.oschina.common.utils.StreamUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.lang.reflect.Type; +import java.util.Date; + +/** + * 详情页面缓存 + * Created by haibin + * on 2016/12/29. + */ +@SuppressWarnings("All") +public final class DetailCache { + private static final long ONE_DAY = 86400000;//1天毫秒 + private static String CUSTOM_CACHE; + private static String COLLECTION_CACHE; + + public static void init(Context context) { + CUSTOM_CACHE = context.getCacheDir() + "/" + "custom_cache/"; + COLLECTION_CACHE = context.getCacheDir() + "/" + "collection_cache/"; + update(false); + update(true); + } + + /** + * 更新文件夹过期的文件 + * + * @param isCollection 是否是收藏 文件夹名 custom_cache | collection_cache + */ + private static void update(boolean isCollection) { + File file = new File(isCollection ? COLLECTION_CACHE : CUSTOM_CACHE); + if (!file.exists()) { + file.mkdirs(); + return; + } + File[] files = file.listFiles(); + long currentDate = new Date().getTime();//当前时间 + long delayDate = (isCollection ? 10 : 2) * ONE_DAY; + for (File f : files) { + if (currentDate - f.lastModified() >= delayDate) { + f.delete(); + } + } + } + + /** + * 添加到缓存文件 + */ + static void addCache(SubBean bean) { + if (bean == null) + return; + String name = bean.getId() + String.valueOf(bean.getType()); + String path = (bean.isFavorite() ? COLLECTION_CACHE : CUSTOM_CACHE) + + name; + File file = new File(path); + FileOutputStream os = null; + try { + if (!file.exists()) + file.createNewFile(); + os = new FileOutputStream(file); + os.write(new Gson().toJson(bean).getBytes()); + os.flush(); + os.close(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + StreamUtil.close(os); + } + } + + /** + * 读取缓存 + */ + static SubBean readCache(SubBean bean) { + if (bean == null || bean.getId() <= 0) + return null; + String path = (bean.isFavorite() ? COLLECTION_CACHE : CUSTOM_CACHE) + + bean.getId() + String.valueOf(bean.getType()); + File file = new File(path); + FileReader reader = null; + try { + reader = new FileReader(file); + Type type = new TypeToken() { + }.getType(); + SubBean subBean = new Gson().fromJson(reader, type); + reader.close(); + return subBean; + } catch (Exception e) { + TLog.error(e.getMessage()); + return null; + } finally { + StreamUtil.close(reader); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/v2/DetailContract.java b/app/src/main/java/net/oschina/app/improve/detail/v2/DetailContract.java new file mode 100644 index 0000000000000000000000000000000000000000..b7b6330e90be2a108e61fb8f7366ca347c40c505 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/v2/DetailContract.java @@ -0,0 +1,124 @@ +package net.oschina.app.improve.detail.v2; + +import net.oschina.app.improve.base.BasePresenter; +import net.oschina.app.improve.base.BaseView; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.detail.db.Behavior; +import net.oschina.app.improve.detail.pay.wx.WeChatPay; + +import java.util.List; + +/** + * Created by haibin + * on 2016/11/30. + */ +@SuppressWarnings("unused") +interface DetailContract { + + interface EmptyView { + void hideEmptyLayout(); + + void showErrorLayout(int errorType); + + void showGetDetailSuccess(SubBean bean); + + void showFavReverseSuccess(boolean isFav, int favCount, int strId); + + void showCommentSuccess(Comment comment); + + void showCommentError(String message); + + void showUploadBehaviorsSuccess(int index, String time); + + void showShareCommentView(Comment comment); + } + + interface View extends BaseView { + void showGetDetailSuccess(SubBean bean); + + void showFavReverseSuccess(boolean isFav, int favCount, int strId); + + void showFavError(); + + void showCommentSuccess(Comment comment); + + void showCommentError(String message); + + void showAddRelationSuccess(boolean isRelation, int strId); + + void showAddRelationError(); + + void showScrollToTop(); + + void showPayDonateSuccess(int type, String sign, WeChatPay.PayResult result); + + void showPayDonateError(); + + /** + * 刷新成功 + */ + void onRefreshSuccess(List

    data); + + /** + * 加载成功 + */ + void onLoadMoreSuccess(List
    data); + + /** + * 没有更多数据 + */ + void showMoreMore(); + + /** + * 加载完成 + */ + void onComplete(); + } + + interface Presenter extends BasePresenter { + + void getCache(); + + void getDetail();//获得详情 + + void favReverse(); + + void addComment( + long sourceId, + int type, + String content, + long referId, + long replyId, + long reAuthorId + );//添加评论 + + void uploadBehaviors(List behaviors); + + void addUserRelation(long authorId); + + void scrollToTop(); + + void shareComment(Comment comment); + + /** + * 支付打赏接口信息拉取 + * @param authorId 被打赏作者 + * @param objId 文章id + * @param money 价格,支付宝单位元、微信单位分 + * @param payType 支付类型 1 支付宝 2、微信支付 返回结果不一样 + */ + void payDonate( long authorId,long objId,float money,int payType); + + /** + * 刷新获得相关推荐 + */ + void onRefresh(); + + /** + * 加载更多获得相关推荐 + */ + void onLoadMore(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/v2/DetailFragment.java b/app/src/main/java/net/oschina/app/improve/detail/v2/DetailFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..b9b9437e022c2125d55d1b0626347b79b0606ada --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/v2/DetailFragment.java @@ -0,0 +1,439 @@ +package net.oschina.app.improve.detail.v2; + +import android.annotation.SuppressLint; +import android.support.v4.widget.NestedScrollView; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.widget.TextView; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.Tag; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.detail.pay.alipay.Alipay; +import net.oschina.app.improve.detail.pay.wx.WeChatPay; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.main.synthesize.article.ArticleAdapter; +import net.oschina.app.improve.main.synthesize.detail.ArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.web.WebActivity; +import net.oschina.app.improve.utils.QuickOptionDialogHelper; +import net.oschina.app.improve.utils.ReadedIndexCacheManager; +import net.oschina.app.improve.widget.OWebView; +import net.oschina.app.improve.widget.RecyclerRefreshLayout; +import net.oschina.app.improve.widget.ScreenView; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.UIHelper; +import net.oschina.common.widget.FlowLayout; + +import java.util.List; + +/** + * Created by haibin + * on 2016/11/30. + */ + +public abstract class DetailFragment extends BaseFragment implements + DetailContract.View, + View.OnClickListener, + BaseRecyclerAdapter.OnItemClickListener { + private OSCApplication.ReadState mReadState; + protected DetailContract.Presenter mPresenter; + protected OWebView mWebView; + protected SubBean mBean; + protected CommentView mCommentView; + + protected int CACHE_CATALOG; + protected NestedScrollView mViewScroller; + protected ScreenView mScreenView; + private ArticleAdapter mAdapter; + private RecyclerRefreshLayout mRefreshLayout; + + protected RecyclerView mRecyclerView; + protected View mHeaderView; + protected FlowLayout mFlowLayout; + + @Override + protected int getLayoutId() { + return R.layout.fragment_detail_v2; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mReadState = OSCApplication.getReadState("sub_list"); + mHeaderView = getHeaderView(); + if (mHeaderView != null) { + mFlowLayout = (FlowLayout) mHeaderView.findViewById(R.id.flowLayout); + mWebView = (OWebView) mHeaderView.findViewById(R.id.webView); + mCommentView = (CommentView) mHeaderView.findViewById(R.id.cv_comment); + mViewScroller = (NestedScrollView) mHeaderView.findViewById(R.id.lay_nsv); + mScreenView = (ScreenView) mHeaderView.findViewById(R.id.screenView); + } else { + mFlowLayout = (FlowLayout) mRoot.findViewById(R.id.flowLayout); + mWebView = (OWebView) mRoot.findViewById(R.id.webView); + mCommentView = (CommentView) mRoot.findViewById(R.id.cv_comment); + mViewScroller = (NestedScrollView) mRoot.findViewById(R.id.lay_nsv); + mScreenView = (ScreenView) mRoot.findViewById(R.id.screenView); + } + mRefreshLayout = (RecyclerRefreshLayout) mRoot.findViewById(R.id.refreshLayout); + mRecyclerView = (RecyclerView) mRoot.findViewById(R.id.recyclerView); + if (mRecyclerView != null && mRefreshLayout != null) { + mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext)); + if (mHeaderView != null) { + mAdapter = new ArticleAdapter(mContext, BaseRecyclerAdapter.BOTH_HEADER_FOOTER); + mAdapter.setHeaderView(mHeaderView); + } else { + mAdapter = new ArticleAdapter(mContext, BaseRecyclerAdapter.ONLY_FOOTER); + } + + mAdapter.setOnItemClickListener(this); + mRecyclerView.setAdapter(mAdapter); + mRecyclerView.setNestedScrollingEnabled(false); + mRefreshLayout.setSuperRefreshLayoutListener(new RecyclerRefreshLayout.SuperRefreshLayoutListener() { + @Override + public void onRefreshing() { + mRefreshLayout.setRefreshing(true); + mRefreshLayout.setOnLoading(true); + if (mPresenter != null) { + mPresenter.onRefresh(); + } + } + + @Override + public void onLoadMore() { + if (mAdapter != null) { + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + } + if (mPresenter != null) { + mPresenter.onLoadMore(); + } + } + + @Override + public void onScrollToBottom() { + if (mAdapter != null) { + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + } + if (mPresenter != null) { + mPresenter.onLoadMore(); + } + } + }); + mRefreshLayout.post(new Runnable() { + @Override + public void run() { + if (mPresenter != null) { + mPresenter.onRefresh(); + } + } + }); + } + } + + @Override + public void onClick(View v) { + + } + + @Override + public void onItemClick(int position, long itemId) { + Article top = mAdapter.getItem(position); + if (top == null) + return; + if (top.getType() == 0) { + if (TypeFormat.isGit(top)) { + WebActivity.show(mContext, TypeFormat.formatUrl(top)); + } else { + ArticleDetailActivity.show(mContext, top); + } + } else { + try { + int type = top.getType(); + long id = top.getOscId(); + switch (type) { + case News.TYPE_SOFTWARE: + SoftwareDetailActivity.show(mContext, id); + break; + case News.TYPE_QUESTION: + QuestionDetailActivity.show(mContext, id); + break; + case News.TYPE_BLOG: + BlogDetailActivity.show(mContext, id); + break; + case News.TYPE_TRANSLATE: + NewsDetailActivity.show(mContext, id, News.TYPE_TRANSLATE); + break; + case News.TYPE_EVENT: + EventDetailActivity.show(mContext, id); + break; + case News.TYPE_NEWS: + NewsDetailActivity.show(mContext, id); + break; + default: + UIHelper.showUrlRedirect(mContext, top.getUrl()); + break; + } + + } catch (Exception e) { + e.printStackTrace(); + ArticleDetailActivity.show(mContext, top); + } + } + mReadState.put(top.getKey()); + mAdapter.updateItem(position); + } + + @Override + public void setPresenter(DetailContract.Presenter presenter) { + this.mPresenter = presenter; + } + + + @SuppressLint("DefaultLocale") + @Override + public void showGetDetailSuccess(SubBean bean) { + this.mBean = bean; + if (mContext == null) return; + //码云挂件替换 + mBean.setBody(bean.getBody().replaceAll("(|
    )<script src='(//gitee.com/[^>]+)'></script>\\s*(|
    )", + "")); + mWebView.loadDetailDataAsync(bean.getBody(), (Runnable) mContext); + + + if (mFlowLayout != null) { + mFlowLayout.removeAllViews(); + if (bean.getiTags() == null || bean.getiTags().length == 0) { + mFlowLayout.setVisibility(View.GONE); + } else { + mFlowLayout.setVisibility(View.VISIBLE); + for (final Tag tag : bean.getiTags()) { + TextView tvTag = (TextView) getActivity().getLayoutInflater().inflate(R.layout.flowlayout_item, mFlowLayout, false); + if (!TextUtils.isEmpty(tag.getName())) + tvTag.setText(tag.getName()); + mFlowLayout.addView(tvTag); + if (tag.getOscId() != 0) { + tvTag.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + SoftwareDetailActivity.show(mContext, tag.getOscId()); + } + }); + } + } + } + } + + if (mCommentView == null || mBean.getType() == News.TYPE_TRANSLATE) { + if (mCommentView != null) { + mCommentView.setVisibility(View.GONE); + } + return; + } + SubBean.Statistics statistics = bean.getStatistics(); + if (statistics == null) + return; + mCommentView.setShareTitle(mBean.getTitle()); + mCommentView.setTitle(String.format("%s (%d)", getResources().getString(R.string.answer_hint), bean.getStatistics().getComment())); + mCommentView.init(bean.getId(), + bean.getType(), + 2, + bean.getStatistics().getComment(), + getImgLoader(), (CommentView.OnCommentClickListener) mContext); + + + } + + public void onPageFinished() { + + if (mBean == null || mBean.getId() <= 0) return; + final int index = ReadedIndexCacheManager.getIndex(getContext(), mBean.getId(), + CACHE_CATALOG); + if (index != 0) { + if (mViewScroller == null) + return; + mViewScroller.postDelayed(new Runnable() { + @Override + public void run() { + if (mViewScroller == null) + return; + mViewScroller.smoothScrollTo(0, index); + } + }, 250); + } + } + + @Override + public void onRefreshSuccess(List
    data) { + if (mAdapter == null) + return; + mRefreshLayout.setCanLoadMore(true); + mAdapter.resetItem(data); + } + + @Override + public void onLoadMoreSuccess(List
    data) { + if (mAdapter == null) + return; + mAdapter.addAll(data); + if (data != null && data.size() > 0) { + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + } else { + mRefreshLayout.setCanLoadMore(false); + } + } + + @Override + public void showMoreMore() { + if (mAdapter == null) + return; + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + } + + @Override + public void onComplete() { + if (mAdapter == null || mRefreshLayout == null || mHeaderView == null) + return; + mRefreshLayout.onComplete(); + hideOrShowTitle(mAdapter.getItems().size() != 0); + } + + private void hideOrShowTitle(boolean isShow) { + if (isShow) { + mHeaderView.findViewById(R.id.line1).setVisibility(View.VISIBLE); + mHeaderView.findViewById(R.id.line2).setVisibility(View.VISIBLE); + mHeaderView.findViewById(R.id.tv_recommend).setVisibility(View.VISIBLE); + //mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + } else { + mHeaderView.findViewById(R.id.line1).setVisibility(View.GONE); + mHeaderView.findViewById(R.id.line2).setVisibility(View.GONE); + mHeaderView.findViewById(R.id.tv_recommend).setVisibility(View.GONE); + mAdapter.setState(BaseRecyclerAdapter.STATE_HIDE, true); + } + } + + @Override + public void showPayDonateError() { + if (mContext == null) + return; + SimplexToast.show(mContext, "获取支付信息失败"); + } + + @Override + public void showPayDonateSuccess(int type, String sign, WeChatPay.PayResult result) { + if (mContext == null) + return; + if (type == 1) { + new Alipay(getActivity()).payV2(sign); + } else { + WeChatPay pay = new WeChatPay(getActivity()); + if (!pay.isWxAppInstalled()) { + SimplexToast.show(mContext, "请安装微信"); + return; + } + pay.pay(result); + } + } + + @Override + public void showFavReverseSuccess(boolean isFav, int favCount, int strId) { + SimplexToast.show(mContext, mContext.getResources().getString(strId)); + } + + @Override + public void showFavError() { + if (mContext == null) + return; + SimplexToast.show(mContext, "收藏失败"); + } + + @Override + public void showNetworkError(int strId) { + if (mContext == null) + return; + SimplexToast.show(mContext, mContext.getResources().getString(strId)); + } + + + @SuppressLint("DefaultLocale") + @Override + public void showCommentSuccess(Comment comment) { + if (mCommentView == null) + return; + mBean.getStatistics().setComment(mBean.getStatistics().getComment() + 1); + mCommentView.setTitle(String.format("%s (%d)", getResources().getString(R.string.answer_hint), mBean.getStatistics().getComment())); + mCommentView.init(mBean.getId(), + mBean.getType(), + 2, + mBean.getStatistics().getComment(), + getImgLoader(), (CommentView.OnCommentClickListener) mContext); + } + + @Override + public void showCommentError(String message) { + + } + + @Override + public void showAddRelationSuccess(boolean isRelation, int strId) { + + } + + @Override + public void showAddRelationError() { + SimplexToast.show(mContext, "关注失败"); + } + + protected String getExtraString(Object object) { + return object == null ? "" : object.toString(); + } + + @SuppressWarnings("unused") + protected abstract int getCommentOrder(); + + @Override + public void onDestroy() { + if (mBean != null && mBean.getId() > 0 && mViewScroller != null) { + ReadedIndexCacheManager.saveIndex(getContext(), mBean.getId(), CACHE_CATALOG, + (mScreenView != null && mScreenView.isViewInScreen()) ? 0 : mViewScroller.getScrollY()); + } + mWebView.destroy(); + super.onDestroy(); + } + + protected void showCopyTitle() { + if (mBean == null) + return; + final String text = mBean.getTitle(); + if (TextUtils.isEmpty(text)) + return; + QuickOptionDialogHelper.with(getContext()) + .addCopy(HTMLUtil.delHTMLTag(text)) + .show(); + } + + @Override + public void showScrollToTop() { + if (mViewScroller != null) + mViewScroller.scrollTo(0, 0); + if (mRecyclerView != null) { + mRecyclerView.scrollToPosition(0); + } + } + + protected View getHeaderView() { + return null; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/v2/DetailPresenter.java b/app/src/main/java/net/oschina/app/improve/detail/v2/DetailPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..f859497edb1968b814cfac4bd70ac9cea8270b94 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/v2/DetailPresenter.java @@ -0,0 +1,403 @@ +package net.oschina.app.improve.detail.v2; + +import android.text.TextUtils; +import android.util.Log; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.Collection; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.simple.UserRelation; +import net.oschina.app.improve.detail.db.API; +import net.oschina.app.improve.detail.db.Behavior; +import net.oschina.app.improve.detail.pay.wx.WeChatPay; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.improve.user.helper.ContactsCacheManager; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.common.utils.CollectionUtil; + +import java.lang.reflect.Type; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2016/11/30. + */ +@SuppressWarnings("all") +public class DetailPresenter implements DetailContract.Presenter { + private final DetailContract.View mView; + private final DetailContract.EmptyView mEmptyView; + private SubBean mBean; + private SubBean mCacheBean; + private String mIdent; + private String mNextToken; + DetailPresenter(DetailContract.View mView, DetailContract.EmptyView mEmptyView, SubBean bean, String ident) { + this.mView = mView; + this.mBean = bean; + this.mIdent = ident; + this.mEmptyView = mEmptyView; + this.mView.setPresenter(this); + } + + @Override + public void getCache() { + mCacheBean = DetailCache.readCache(mBean); + if (mCacheBean == null) + return; + mView.showGetDetailSuccess(mCacheBean); + mEmptyView.showGetDetailSuccess(mCacheBean); + } + + @Override + public void getDetail() { + OSChinaApi.getDetail(mBean.getType(), mIdent, mBean.getId(), new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (mCacheBean != null) + return; + mEmptyView.showErrorLayout(EmptyLayout.NETWORK_ERROR); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = AppOperator.createGson().fromJson(responseString, type); + if (bean.isSuccess()) { + mBean = bean.getResult(); + mView.showGetDetailSuccess(mBean); + mEmptyView.showGetDetailSuccess(mBean); + } else { + if (mCacheBean != null) + return; + mEmptyView.showErrorLayout(EmptyLayout.NODATA); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onCancel() { + super.onCancel(); + if (mCacheBean != null) + return; + mEmptyView.showErrorLayout(EmptyLayout.NETWORK_ERROR); + } + }); + } + + @Override + public void favReverse() { + OSChinaApi.getFavReverse(mBean.getId(), mBean.getType(), new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showFavError(); + mView.showNetworkError(R.string.tip_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean != null && resultBean.isSuccess()) { + Collection collection = resultBean.getResult(); + mBean.setFavorite(collection.isFavorite()); + mBean.getStatistics().setFavCount(collection.getFavCount()); + mView.showFavReverseSuccess(collection.isFavorite(), collection.getFavCount(), collection.isFavorite() ? R.string.add_favorite_success : R.string.del_favorite_success); + mEmptyView.showFavReverseSuccess(collection.isFavorite(), collection.getFavCount(), collection.isFavorite() ? R.string.add_favorite_success : R.string.del_favorite_success); + } else { + mView.showFavError(); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + }); + } + + @Override + public void addComment(long sourceId, int type, String content, long referId, long replyId, long reAuthorId) { + OSChinaApi.pubComment(sourceId, type, content, referId, replyId, reAuthorId, new TextHttpResponseHandler() { + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.tip_network_error); + mEmptyView.showCommentError(""); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + Comment respComment = resultBean.getResult(); + if (respComment != null) { + mView.showCommentSuccess(respComment); + mEmptyView.showCommentSuccess(respComment); + } + } else { + mView.showCommentError(resultBean.getMessage()); + mEmptyView.showCommentError(resultBean.getMessage()); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + mView.showCommentError("评论失败"); + mEmptyView.showCommentError("评论失败"); + } + } + + @Override + public void onStart() { + super.onStart(); + SubBean subBean = mBean; + if (subBean != null) + ContactsCacheManager.addRecentCache(subBean.getAuthor()); + } + }); + } + + @Override + public void addUserRelation(long authorId) { + OSChinaApi.addUserRelationReverse(authorId, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showAddRelationError(); + mView.showNetworkError(R.string.tip_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean != null && resultBean.isSuccess()) { + int relation = resultBean.getResult().getRelation(); + mBean.getAuthor().setRelation(relation); + boolean isRelation = relation == UserRelation.RELATION_ALL + || relation == UserRelation.RELATION_ONLY_YOU; + mView.showAddRelationSuccess(isRelation, + isRelation ? R.string.add_relation_success : R.string.cancel_relation_success); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showAddRelationError(); + } + } + }); + } + + @Override + public void uploadBehaviors(final List behaviors) { + API.addBehaviors(new Gson().toJson(behaviors), new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + // TODO: 2017/5/25 不需要处理失败的情况 + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.getCode() == 1) { + mEmptyView.showUploadBehaviorsSuccess(behaviors.get(behaviors.size() - 1).getId() + , bean.getTime()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + + @Override + public void onRefresh() { + OSChinaApi.getArticleRecommends( + String.format("osc_%s_%s",mBean.getType(),mBean.getId()), + OSCSharedPreference.getInstance().getDeviceUUID(), + "", + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + try { + mView.showMoreMore(); + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + PageBean
    pageBean = bean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List
    list = pageBean.getItems(); + for (Article article : list) { + article.setImgs(removeImgs(article.getImgs())); + } + mView.onRefreshSuccess(list); + if (list.size() == 0) { + mView.showMoreMore(); + } + } else { + mView.showMoreMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showMoreMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + OSChinaApi.getArticleRecommends( + String.format("osc_%s_%s",mBean.getType(),mBean.getId()), + OSCSharedPreference.getInstance().getDeviceUUID(), + mNextToken, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + try { + mView.showMoreMore(); + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + PageBean
    pageBean = bean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List
    list = pageBean.getItems(); + for (Article article : list) { + article.setImgs(removeImgs(article.getImgs())); + } + mView.onLoadMoreSuccess(list); + if (list.size() == 0) { + mView.showMoreMore(); + } + } else { + mView.showMoreMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showMoreMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void shareComment(Comment comment) { + mEmptyView.showShareCommentView(comment); + } + + boolean isHideCommentBar() { + return mBean.getType() == News.TYPE_EVENT; + } + + @Override + public void scrollToTop() { + mView.showScrollToTop(); + } + + + @Override + public void payDonate(long authorId, long objId, float money, final int payType) { + DecimalFormat decimalFormat = new DecimalFormat(".00"); + OSChinaApi.getPayDonate(authorId, objId, (float) (Math.round(money * 100) / 100), payType, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showPayDonateError(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = null; + if (payType == 1) { + type = new TypeToken>() { + }.getType(); + ResultBean resultBean = new Gson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + mView.showPayDonateSuccess(payType, resultBean.getResult(), null); + } else { + mView.showPayDonateError(); + } + } else { + type = new TypeToken>() { + }.getType(); + ResultBean resultBean = new Gson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + mView.showPayDonateSuccess(payType, null, resultBean.getResult()); + } else { + mView.showPayDonateError(); + } + } + + } catch (Exception e) { + e.printStackTrace(); + mView.showPayDonateError(); + } + } + }); + } + + private static String[] removeImgs(String[] imgs) { + if (imgs == null || imgs.length == 0) + return null; + List list = new ArrayList<>(); + for (String img : imgs) { + if (!TextUtils.isEmpty(img)) { + if (img.startsWith("http")) { + list.add(img); + } + } + } + return CollectionUtil.toArray(list, String.class); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/detail/v2/ReportDialog.java b/app/src/main/java/net/oschina/app/improve/detail/v2/ReportDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..d7e99c9930da331c374386c57e3df78fe8e10235 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/detail/v2/ReportDialog.java @@ -0,0 +1,102 @@ +package net.oschina.app.improve.detail.v2; + +import android.content.Context; +import android.content.DialogInterface; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; +import android.widget.TextView; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.Report; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.SimplexToast; + +import java.lang.reflect.Type; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2016/12/26. + */ +@SuppressWarnings("all") +public class ReportDialog { + public static AlertDialog create(final Context context, + final long id, + final String href, + final byte type, + final String key) { + View view = LayoutInflater.from(context).inflate(R.layout.dialog_report_view, null); + TextView textLink = (TextView) view.findViewById(R.id.tv_link); + final TextView textType = (TextView) view.findViewById(R.id.tv_report_type); + final EditText editText = (EditText) view.findViewById(R.id.et_report); + final String[] reason = context.getResources().getStringArray(R.array.report); + textType.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + DialogHelper.getSelectDialog(context, + reason, + "取消", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + textType.setTag(which); + textType.setText(reason[which]); + } + }).show(); + } + }); + textLink.setText(href); + textType.setText("广告"); + textType.setTag(1); + AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.App_Theme_Dialog_Alert) + .setView(view) + .setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int tag = Integer.parseInt(textType.getTag().toString()); + if (tag == 0 && TextUtils.isEmpty(editText.getText().toString().trim())) { + SimplexToast.show(context, "请填写其它原因"); + return; + } + OSChinaApi.report(id, + type, + href, + Integer.parseInt(textType.getTag().toString()), + editText.getText().toString(), + key, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + SimplexToast.show(context, "举报失败"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type t = new TypeToken>() { + }.getType(); + ResultBean resultBean = new Gson().fromJson(responseString, t); + if (resultBean != null) { + SimplexToast.show(context, resultBean.isSuccess() ? "举报成功" : resultBean.getMessage()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + }) + .setNegativeButton("取消", null); + return builder.create(); + } +} diff --git a/app/src/main/java/net/oschina/app/ui/EventApplyDialog.java b/app/src/main/java/net/oschina/app/improve/dialog/EventDetailApplyDialog.java similarity index 52% rename from app/src/main/java/net/oschina/app/ui/EventApplyDialog.java rename to app/src/main/java/net/oschina/app/improve/dialog/EventDetailApplyDialog.java index 0a87f5dea75d6487094944efcc430dd412210917..47dbca3701fff2d8d017c94a8c7ed63368f6a877 100644 --- a/app/src/main/java/net/oschina/app/ui/EventApplyDialog.java +++ b/app/src/main/java/net/oschina/app/improve/dialog/EventDetailApplyDialog.java @@ -1,89 +1,85 @@ -package net.oschina.app.ui; +package net.oschina.app.improve.dialog; -import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.text.TextUtils; import android.view.View; import android.widget.EditText; +import android.widget.RadioButton; import android.widget.TextView; import net.oschina.app.AppContext; import net.oschina.app.R; -import net.oschina.app.bean.Event; import net.oschina.app.bean.EventApplyData; +import net.oschina.app.improve.bean.EventDetail; +import net.oschina.app.improve.utils.DialogHelper; import net.oschina.app.ui.dialog.CommonDialog; -import net.oschina.app.util.DialogHelp; +import java.util.Arrays; import java.util.List; +import java.util.regex.Pattern; +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; -public class EventApplyDialog extends CommonDialog implements - android.view.View.OnClickListener { +public class EventDetailApplyDialog extends CommonDialog implements + View.OnClickListener { - @InjectView(R.id.et_name) + @Bind(R.id.et_name) EditText mName; - @InjectView(R.id.tv_gender) - TextView mGender; + @Bind(R.id.rb_male) + RadioButton rb_male; private String[] genders; - @InjectView(R.id.et_phone) + @Bind(R.id.et_phone) EditText mMobile; - @InjectView(R.id.et_company) + @Bind(R.id.et_company) EditText mCompany; - @InjectView(R.id.et_job) + @Bind(R.id.et_job) EditText mJob; - @InjectView(R.id.tv_remarks_tip) + @Bind(R.id.tv_remarks_tip) TextView mTvRemarksTip;// 备注提示 - @InjectView(R.id.tv_remarks_selecte) + @Bind(R.id.tv_remarks_selecte) TextView mTvRemarksSelected;// 备注选择 - private Event mEvent; + private EventDetail mEvent; - private EventApplyDialog(Context context, boolean flag, OnCancelListener listener) { + private EventDetailApplyDialog(Context context, boolean flag, OnCancelListener listener) { super(context, flag, listener); } - @SuppressLint("InflateParams") - private EventApplyDialog(Context context, int defStyle, Event event) { + private EventDetailApplyDialog(Context context, int defStyle, EventDetail event) { super(context, defStyle); - View shareView = getLayoutInflater().inflate( - R.layout.dialog_event_apply, null); - ButterKnife.inject(this, shareView); + View shareView = View.inflate(context, R.layout.dialog_event_detail_apply, null); + ButterKnife.bind(this, shareView); setContent(shareView, 0); this.mEvent = event; initView(); } private void initView() { - genders = getContext().getResources().getStringArray( - R.array.gender); + genders = getContext().getResources().getStringArray(R.array.gender); - mGender.setText(genders[0]); + rb_male.setChecked(true); - mGender.setOnClickListener(this); - - - if (mEvent.getEventRemark() != null) { + if (mEvent.getRemark() != null) { mTvRemarksTip.setVisibility(View.VISIBLE); - mTvRemarksTip.setText(mEvent.getEventRemark().getRemarkTip()); + mTvRemarksTip.setText(mEvent.getRemark().getTip()); mTvRemarksSelected.setVisibility(View.VISIBLE); mTvRemarksSelected.setOnClickListener(this); - - mTvRemarksSelected.setText(mEvent.getEventRemark().getSelect().getList().get(0)); + String[] selects = mEvent.getRemark().getSelect().split(","); + mTvRemarksSelected.setText(selects.length > 0 ? selects[0] : mEvent.getRemark().getSelect()); } } - public EventApplyDialog(Context context, Event event) { + public EventDetailApplyDialog(Context context, EventDetail event) { this(context, R.style.dialog_bottom, event); } @@ -91,9 +87,6 @@ public class EventApplyDialog extends CommonDialog implements public void onClick(View v) { int id = v.getId(); switch (id) { - case R.id.tv_gender: - selectGender(); - break; case R.id.tv_remarks_selecte: selectRemarkSelect(); break; @@ -102,30 +95,14 @@ public class EventApplyDialog extends CommonDialog implements } } - private void selectGender() { - String gender = mGender.getText().toString(); - int idx = 0; - for (int i = 0; i < genders.length; i++) { - if (genders[i].equals(gender)) { - idx = i; - break; - } - } - DialogHelp.getSelectDialog(getContext(), genders, new OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - mGender.setText(genders[i]); - } - }).show(); - } private void selectRemarkSelect() { - List stringList = mEvent.getEventRemark().getSelect().getList(); + List stringList = Arrays.asList(mEvent.getRemark().getSelect().split(",")); final String[] remarkSelects = new String[stringList.size()]; for (int i = 0; i < stringList.size(); i++) { remarkSelects[i] = stringList.get(i); } - DialogHelp.getSelectDialog(getContext(), remarkSelects, new OnClickListener() { + DialogHelper.getSelectDialog(getContext(), remarkSelects, "取消", new OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { mTvRemarksSelected.setText(remarkSelects[i]); @@ -135,7 +112,7 @@ public class EventApplyDialog extends CommonDialog implements public EventApplyData getApplyData() { String name = mName.getText().toString(); - String gender = mGender.getText().toString(); + String gender = genders[rb_male.isChecked() ? 0 : 1]; String phone = mMobile.getText().toString(); String company = mCompany.getText().toString(); String job = mJob.getText().toString(); @@ -151,8 +128,14 @@ public class EventApplyDialog extends CommonDialog implements return null; } - if (mEvent.getEventRemark() != null && TextUtils.isEmpty(remark)) { - AppContext.showToast("请" + mEvent.getEventRemark().getRemarkTip()); + Pattern pattern = Pattern.compile("^1(3[0-9]|4[57]|5[0-35-9]|7[01678]|8[0-9])[0-9]{7}[1-9]"); + if (!pattern.matcher(phone).find()) { + AppContext.showToast("请填写正确的手机号码"); + return null; + } + + if (mEvent.getRemark() != null && TextUtils.isEmpty(remark)) { + AppContext.showToast("请" + mEvent.getRemark().getTip()); return null; } @@ -164,7 +147,7 @@ public class EventApplyDialog extends CommonDialog implements data.setCompany(company); data.setJob(job); data.setRemark(remark); - + dismiss(); return data; } } diff --git a/app/src/main/java/net/oschina/app/improve/dialog/ShareDialog.java b/app/src/main/java/net/oschina/app/improve/dialog/ShareDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..2e3a572ced481002b8765d98387d8e1ed20f579d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/dialog/ShareDialog.java @@ -0,0 +1,582 @@ +package net.oschina.app.improve.dialog; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.v4.content.FileProvider; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.bumptech.glide.Glide; +import com.tencent.tauth.IUiListener; +import com.tencent.tauth.UiError; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.detail.share.ShareActivity; +import net.oschina.app.improve.media.ImageGalleryActivity; +import net.oschina.app.improve.tweet.activities.TweetPublishActivity; +import net.oschina.app.improve.tweet.share.TweetShareActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.BottomDialog; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.TDevice; +import net.oschina.common.utils.StreamUtil; +import net.oschina.open.bean.Share; +import net.oschina.open.constants.OpenConstant; +import net.oschina.open.factory.OpenBuilder; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * Created by haibin + * on 2016/12/28. + */ +@SuppressWarnings("all") +public class ShareDialog extends BottomDialog implements OpenBuilder.Callback, + DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { + private Activity mActivity; + private ProgressDialog mDialog; + private About.Share mAboutShare; + private Share mShare; + private boolean isOnlyBitmap; + private boolean isShareDetail; + private boolean isShareTweet; + private SubBean mBean; + private Tweet mTweet; + + private ShareItemClickListener mItemClickListener; + + public ShareDialog(@NonNull Activity activity) { + super(activity, true); + this.mActivity = activity; + LayoutInflater inflater = LayoutInflater.from(getContext()); + View contentView = inflater.inflate(R.layout.dialog_share_main, null, false); + RecyclerView shareRecycle = (RecyclerView) contentView.findViewById(R.id.share_recycler); + final ShareActionAdapter adapter = new ShareActionAdapter(activity); + adapter.addAll(getAdapterData()); + adapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + ShareDialog.this.onItemClick(position, adapter.getItem(position)); + } + }); + shareRecycle.setAdapter(adapter); + shareRecycle.setItemAnimator(new DefaultItemAnimator()); + shareRecycle.setLayoutManager(new GridLayoutManager(getContext(), 4)); + setContentView(contentView); + setOnCancelListener(this); + setOnDismissListener(this); + mShare = new Share(); + mShare.setAppName("开源中国"); + } + + public ShareDialog(@NonNull Activity activity, boolean isShareTweet) { + super(activity, true); + this.mActivity = activity; + this.isShareTweet = isShareTweet; + LayoutInflater inflater = LayoutInflater.from(getContext()); + View contentView = inflater.inflate(R.layout.dialog_share_main, null, false); + RecyclerView shareRecycle = (RecyclerView) contentView.findViewById(R.id.share_recycler); + final ShareActionAdapter adapter = new ShareActionAdapter(activity); + adapter.addAll(getAdapterData()); + adapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + ShareDialog.this.onItemClick(position, adapter.getItem(position)); + } + }); + shareRecycle.setAdapter(adapter); + shareRecycle.setItemAnimator(new DefaultItemAnimator()); + shareRecycle.setLayoutManager(new GridLayoutManager(getContext(), 4)); + setContentView(contentView); + setOnCancelListener(this); + setOnDismissListener(this); + mShare = new Share(); + mShare.setAppName("开源中国"); + } + + public ShareDialog(@NonNull Activity activity, long id, boolean isShareDetail) { + this(activity); + this.isShareDetail = isShareDetail; + this.mActivity = activity; + mAboutShare = new About.Share(); + mAboutShare.id = id; + if (id < 0) + isOnlyBitmap = true; + LayoutInflater inflater = LayoutInflater.from(getContext()); + View contentView = inflater.inflate(R.layout.dialog_share_main, null, false); + RecyclerView shareRecycle = (RecyclerView) contentView.findViewById(R.id.share_recycler); + final ShareActionAdapter adapter = new ShareActionAdapter(activity); + adapter.addAll(getAdapterData()); + adapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + ShareDialog.this.onItemClick(position, adapter.getItem(position)); + } + }); + shareRecycle.setAdapter(adapter); + shareRecycle.setItemAnimator(new DefaultItemAnimator()); + shareRecycle.setLayoutManager(new GridLayoutManager(getContext(), 4)); + setContentView(contentView); + setOnCancelListener(this); + setOnDismissListener(this); + mShare = new Share(); + mShare.setAppName("开源中国"); + } + + public void setBean(SubBean mBean) { + this.mBean = mBean; + } + + public void setTweet(Tweet mTweet) { + this.mTweet = mTweet; + } + + public void setItemClickListener(ShareItemClickListener listener) { + this.mItemClickListener = listener; + } + + @Override + public void onCancel(DialogInterface dialog) { + hideWaitDialog(); + } + + @Override + public void onDismiss(DialogInterface dialog) { + hideWaitDialog(); + } + + private List getAdapterData() { + List shareActions = new ArrayList<>(); + + //0.新浪微博 + shareActions.add(new ShareItem(R.mipmap.ic_login_3party_weibo, R.string.platform_sina)); + + //1.朋友圈 + shareActions.add(new ShareItem(R.mipmap.ic_action_moments, R.string.platform_wechat_circle)); + + //2.微信 + shareActions.add(new ShareItem(R.mipmap.ic_login_3party_wechat, R.string.platform_wechat)); + + //3.QQ + shareActions.add(new ShareItem(R.mipmap.ic_login_3party_qq, R.string.platform_qq)); + + if (isOnlyBitmap) { + shareActions.add(new ShareItem(R.mipmap.ic_action_tweet, R.string.platform_tweet)); + shareActions.add(new ShareItem(R.mipmap.ic_action_preview, R.string.platform_preview)); + return shareActions; + } + //4.动弹 + if (About.check(mAboutShare)) { + shareActions.add(new ShareItem(R.mipmap.ic_action_tweet, R.string.platform_tweet)); + } + + //5.browser + shareActions.add(new ShareItem(R.mipmap.ic_action_browser, R.string.platform_browser)); + + if (isShareDetail || isShareTweet) { + // + shareActions.add(new ShareItem(R.mipmap.ic_action_screenshot, R.string.platform_picture)); + } else { + //6.复制链接 + shareActions.add(new ShareItem(R.mipmap.ic_action_url, R.string.platform_copy_link)); + } + + //7.更多 + shareActions.add(new ShareItem(R.mipmap.ic_action_more, R.string.platform_more_option)); + + return shareActions; + } + + public ShareDialog with() { + mShare.setAppShareIcon(R.mipmap.ic_share_app_logo); + if (mShare.getBitmapResID() == 0) + mShare.setBitmapResID(R.mipmap.ic_share_app_logo); + return this; + } + + public ShareDialog title(String title) { + mShare.setTitle(title); + if (mAboutShare == null) + mAboutShare = new About.Share(); + mAboutShare.title = title; + return this; + } + + public ShareDialog summary(String summary) { + mShare.setSummary(summary); + mAboutShare.content = summary; + return this; + } + + public ShareDialog content(String content) { + mShare.setContent(content); + summary(content); + description(content); + mAboutShare.content = content; + return this; + } + + public ShareDialog description(String description) { + mShare.setDescription(description); + return this; + } + + public ShareDialog url(String url) { + mShare.setUrl(url); + return this; + } + + public ShareDialog bitmapResID(int bitmapResID) { + mShare.setBitmapResID(bitmapResID); + return this; + } + + public ShareDialog bitmap(Bitmap bitmap) { + mShare.setThumbBitmap(bitmap); + return this; + } + + public ShareDialog imageUrl(final String imageUrl) { + mShare.setImageUrl(imageUrl); + + if (!TextUtils.isEmpty(imageUrl)) { + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + try { + Bitmap thumbBitmap = Glide.with(getContext()) + .load(imageUrl) + .asBitmap().into(100, 100).get(); + //为微博和微信加入分享的详情icon + + mShare.setThumbBitmap(thumbBitmap); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + }); + } + + return this; + } + + + public ShareDialog id(long id) { + mAboutShare.id = id; + return this; + } + + public ShareDialog type(int type) { + mAboutShare.type = type; + return this; + } + + public Share getShare() { + return mShare; + } + + @Override + public void onFailed() { + + } + + @Override + public void onSuccess() { + + } + + public void onItemClick(int position, ShareItem item) { + final Share share = getShare(); + switch (item.iconId) { + //新浪微博 + case R.mipmap.ic_login_3party_weibo: + showWaitDialog(R.string.login_weibo_hint); + OpenBuilder.with(mActivity) + .useWeibo(OpenConstant.WB_APP_KEY) + .share(share, this); + break; + //朋友圈 + case R.mipmap.ic_action_moments: + showWaitDialog(R.string.login_wechat_hint); + OpenBuilder.with(mActivity) + .useWechat(OpenConstant.WECHAT_APP_ID) + .shareTimeLine(share, this); + break; + //微信会话 + case R.mipmap.ic_login_3party_wechat: + showWaitDialog(R.string.login_wechat_hint); + OpenBuilder.with(mActivity) + .useWechat(OpenConstant.WECHAT_APP_ID) + .shareSession(share, this); + break; + //QQ + case R.mipmap.ic_login_3party_qq: + showWaitDialog(R.string.login_tencent_hint); + OpenBuilder.with(mActivity) + .useTencent(OpenConstant.QQ_APP_ID) + .share(share, new IUiListener() { + @Override + public void onComplete(Object o) { + hideWaitDialog(); + } + + @Override + public void onError(UiError uiError) { + hideWaitDialog(); + AppContext.showToast(R.string.share_hint, Toast.LENGTH_SHORT); + } + + @Override + public void onCancel() { + hideWaitDialog(); + } + }, this); + break; + //转发到动弹 + case R.mipmap.ic_action_tweet: + if (About.check(mAboutShare)) + TweetPublishActivity.show(getContext(), null, null, mAboutShare); + if (isOnlyBitmap && mShare.getThumbBitmap() != null) { + + showWaitDialog(R.string.loading_image); + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + final String url = OpenBuilder.saveShare(share.getThumbBitmap()); + AppOperator.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + if (TextUtils.isEmpty(url)) return; + TweetPublishActivity.show(getContext(), false, url); + cancelLoading(); + hideProgressDialog(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + }); + } + break; + //在浏览器中打开 + case R.mipmap.ic_action_browser: + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + // intent.setAction(Intent.CATEGORY_BROWSABLE); + Uri content_url = Uri.parse(share.getUrl()); + intent.setData(content_url); + mActivity.startActivity(intent); + cancelLoading(); + break; + //复制链接 + case R.mipmap.ic_action_url: + TDevice.copyTextToBoard(share.getUrl()); + cancelLoading(); + break; + case R.mipmap.ic_action_screenshot: + if (isShareTweet) { + if (mItemClickListener != null) { + mItemClickListener.onShareTweet(); + } else { + TweetShareActivity.show(getContext(), mTweet); + } + cancelLoading(); + } else { + ShareActivity.show(getContext(), mBean); + cancelLoading(); + } + break; + //保存到本地 + case R.mipmap.ic_action_preview: + showWaitDialog(R.string.loading_image); + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + final String url = OpenBuilder.saveShare(share.getThumbBitmap()); + AppOperator.runOnMainThread(new Runnable() { + @Override + public void run() { + try { + if (!TextUtils.isEmpty(url)) { + ImageGalleryActivity.show(mActivity, url); + } + cancelLoading(); + hideProgressDialog(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + }); + break; + //更多(调用系统分享) + default: + showSystemShareOption(share.getTitle(), share.getUrl()); + cancelLoading(); + break; + } + } + + public static void saveShare(Activity activity, Bitmap bitmap) { + FileOutputStream os = null; + String url = null; + try { + File file = new File(url = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + .getAbsolutePath() + File.separator + "开源中国/"); + if (!file.exists()) + file.mkdirs(); + url = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + .getAbsolutePath() + File.separator + "开源中国/" + + System.currentTimeMillis() + ".jpg"; + os = new FileOutputStream(url); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + os.flush(); + os.close(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (bitmap != null && !bitmap.isRecycled()) + bitmap.recycle(); + StreamUtil.close(os); + if (!TextUtils.isEmpty(url)) { + Uri uri = Uri.fromFile(new File(url)); + activity.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)); + SimplexToast.show(activity, "保存成功"); + } + } + } + + private void hideWaitDialog() { + ProgressDialog dialog = mDialog; + if (dialog != null) { + mDialog = null; + try { + dialog.cancel(); + // dialog.dismiss(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + private ProgressDialog showWaitDialog(@StringRes int messageId) { + if (mDialog == null) { + mDialog = DialogHelper.getProgressDialog(mActivity); + } + mDialog.setMessage(mActivity.getResources().getString(messageId)); + mDialog.show(); + return mDialog; + } + + public void cancelLoading() { + if (this != null && this.isShowing()) { + this.cancel(); + this.dismiss(); + //mAlertDialog.dismiss(); + } + } + + private void shareSystemImage(String url) { + Intent shareIntent = new Intent(Intent.ACTION_SEND); + Uri uri = FileProvider.getUriForFile(mActivity, "net.oschina.app.provider", new File(url)); + shareIntent.setType("image/*"); + shareIntent.putExtra(Intent.EXTRA_STREAM, uri); +// shareIntent.putExtra(Intent.EXTRA_TEXT, title); + mActivity.startActivity(Intent.createChooser(shareIntent, "分享图片")); + } + + private void showSystemShareOption(final String title, final String url) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_SUBJECT, "分享:" + title); + intent.putExtra(Intent.EXTRA_TEXT, title + " " + url); + getContext().startActivity(Intent.createChooser(intent, "选择分享")); + } + + private class ShareActionAdapter extends BaseRecyclerAdapter { + + ShareActionAdapter(Context context) { + super(context, NEITHER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ShareViewHolder(LayoutInflater.from(mActivity).inflate(R.layout.dialog_share_item, + parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, ShareItem item, int position) { + ShareViewHolder h = (ShareViewHolder) holder; + h.mIvIcon.setImageResource(item.iconId); + h.mTvName.setText(item.nameId); + } + } + + + static class ShareViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.share_icon) + ImageView mIvIcon; + @Bind(R.id.share_name) + TextView mTvName; + + public ShareViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } + + private static class ShareItem { + int iconId; + int nameId; + + ShareItem(int iconId, int nameId) { + this.iconId = iconId; + this.nameId = nameId; + } + } + + public void hideProgressDialog() { + if (mDialog == null) + return; + mDialog.dismiss(); + } + + public interface ShareItemClickListener { + void onShareTweet(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/dialog/ShareDialogBuilder.java b/app/src/main/java/net/oschina/app/improve/dialog/ShareDialogBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..6b5b255e1fbbac34a4fd6fcf152ea70428e5c622 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/dialog/ShareDialogBuilder.java @@ -0,0 +1,460 @@ +package net.oschina.app.improve.dialog; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.annotation.StyleRes; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.Display; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.tencent.tauth.IUiListener; +import com.tencent.tauth.UiError; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.tweet.activities.TweetPublishActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.util.TDevice; +import net.oschina.open.bean.Share; +import net.oschina.open.constants.OpenConstant; +import net.oschina.open.factory.OpenBuilder; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * 分享弹出框辅助类 + */ +public class ShareDialogBuilder extends AlertDialog.Builder implements + DialogInterface.OnCancelListener, DialogInterface.OnDismissListener, + OpenBuilder.Callback { + private Share mShare; + private About.Share mAboutShare; + private Activity mActivity; + private AlertDialog mAlertDialog; + private ProgressDialog mDialog; + + private ShareDialogBuilder(@NonNull Activity context, @StyleRes int themeResId) { + super(context, themeResId); + mActivity = context; + setTitle(null); + } + + public static ShareBuilder with(Activity activity) { + return with(activity, R.style.share_dialog); + } + + public static ShareBuilder with(@NonNull Activity activity, @StyleRes int themeResId) { + return new ShareBuilder(activity, themeResId); + } + + private List getAdapterData() { + List shareActions = new ArrayList<>(); + + //0.新浪微博 + shareActions.add(new ShareItem(R.mipmap.ic_login_3party_weibo, R.string.platform_sina)); + + //1.朋友圈 + shareActions.add(new ShareItem(R.mipmap.ic_action_moments, R.string.platform_wechat_circle)); + + //2.微信 + shareActions.add(new ShareItem(R.mipmap.ic_login_3party_wechat, R.string.platform_wechat)); + + //3.QQ + shareActions.add(new ShareItem(R.mipmap.ic_login_3party_qq, R.string.platform_qq)); + + //4.动弹 + if (About.check(mAboutShare)) { + shareActions.add(new ShareItem(R.mipmap.ic_action_tweet, R.string.platform_tweet)); + } + + //5.browser + shareActions.add(new ShareItem(R.mipmap.ic_action_browser, R.string.platform_browser)); + + //6.复制链接 + shareActions.add(new ShareItem(R.mipmap.ic_action_url, R.string.platform_copy_link)); + + //7.更多 + shareActions.add(new ShareItem(R.mipmap.ic_action_more, R.string.platform_more_option)); + + return shareActions; + } + + @SuppressWarnings("deprecation") + @Override + public AlertDialog create() { + + AlertDialog alertDialog = super.create(); + Window window = alertDialog.getWindow(); + + if (window != null) { + window.setGravity(Gravity.BOTTOM); + WindowManager m = window.getWindowManager(); + Display d = m.getDefaultDisplay(); + WindowManager.LayoutParams p = window.getAttributes(); + p.width = d.getWidth(); + window.setAttributes(p); + } + this.mAlertDialog = alertDialog; + return alertDialog; + } + + @Override + public AlertDialog.Builder setView(int layoutResId) { + AlertDialog.Builder builder = super.setView(layoutResId); + if (layoutResId == R.layout.dialog_share_main) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + View contentView = inflater.inflate(layoutResId, null, false); + RecyclerView shareRecycle = (RecyclerView) contentView.findViewById(R.id.share_recycler); + shareRecycle.setAdapter(new ShareActionAdapter(getAdapterData())); + shareRecycle.setItemAnimator(new DefaultItemAnimator()); + shareRecycle.setLayoutManager(new GridLayoutManager(getContext(), 4)); + builder.setView(contentView); + builder.setOnCancelListener(this); + builder.setOnDismissListener(this); + } + return builder; + } + + public ShareDialogBuilder addShare(Share share) { + this.mShare = share; + return this; + } + + public ShareDialogBuilder boundActivity(Activity activity) { + this.mActivity = activity; + return this; + } + + @Override + public void onCancel(DialogInterface dialog) { + hideWaitDialog(); + } + + @Override + public void onDismiss(DialogInterface dialog) { + hideWaitDialog(); + } + + public void onItemClick(int position, ShareItem item) { + Share share = getShare(); + switch (item.iconId) { + //新浪微博 + case R.mipmap.ic_login_3party_weibo: + showWaitDialog(R.string.login_weibo_hint); + OpenBuilder.with(mActivity) + .useWeibo(OpenConstant.WB_APP_KEY) + .share(share, this); + break; + //朋友圈 + case R.mipmap.ic_action_moments: + showWaitDialog(R.string.login_wechat_hint); + OpenBuilder.with(mActivity) + .useWechat(OpenConstant.WECHAT_APP_ID) + .shareTimeLine(share, this); + break; + //微信会话 + case R.mipmap.ic_login_3party_wechat: + showWaitDialog(R.string.login_wechat_hint); + OpenBuilder.with(mActivity) + .useWechat(OpenConstant.WECHAT_APP_ID) + .shareSession(share, this); + break; + //QQ + case R.mipmap.ic_login_3party_qq: + showWaitDialog(R.string.login_tencent_hint); + OpenBuilder.with(mActivity) + .useTencent(OpenConstant.QQ_APP_ID) + .share(share, new IUiListener() { + @Override + public void onComplete(Object o) { + hideWaitDialog(); + } + + @Override + public void onError(UiError uiError) { + hideWaitDialog(); + AppContext.showToast(R.string.share_hint, Toast.LENGTH_SHORT); + } + + @Override + public void onCancel() { + hideWaitDialog(); + } + }, this); + break; + //转发到动弹 + case R.mipmap.ic_action_tweet: + if (About.check(mAboutShare)) + TweetPublishActivity.show(getContext(), null, null, mAboutShare); + cancelLoading(); + break; + //在浏览器中打开 + case R.mipmap.ic_action_browser: + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + // intent.setAction(Intent.CATEGORY_BROWSABLE); + Uri content_url = Uri.parse(share.getUrl()); + intent.setData(content_url); + mActivity.startActivity(intent); + cancelLoading(); + break; + //复制链接 + case R.mipmap.ic_action_url: + TDevice.copyTextToBoard(share.getUrl()); + cancelLoading(); + break; + //更多(调用系统分享) + default: + showSystemShareOption(share.getTitle(), share.getUrl()); + cancelLoading(); + break; + } + + } + + /** + * show WaitDialog + * + * @return progressDialog + */ + private ProgressDialog showWaitDialog(@StringRes int messageId) { + if (mDialog == null) { + mDialog = DialogHelper.getProgressDialog(mActivity, true); + } + mDialog.setMessage(mActivity.getResources().getString(messageId)); + mDialog.show(); + return mDialog; + } + + /** + * hide waitDialog + */ + private void hideWaitDialog() { + ProgressDialog dialog = mDialog; + if (dialog != null) { + mDialog = null; + try { + dialog.cancel(); + // dialog.dismiss(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + public Share getShare() { + return mShare; + } + + /** + * 调用系统安装的应用分享 + * + * @param title title + * @param url url + */ + private void showSystemShareOption(final String title, final String url) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_SUBJECT, "分享:" + title); + intent.putExtra(Intent.EXTRA_TEXT, title + " " + url); + getContext().startActivity(Intent.createChooser(intent, "选择分享")); + } + + @Override + public void onFailed() { + hideWaitDialog(); + AppContext.showToast(R.string.share_hint, Toast.LENGTH_SHORT); + } + + @Override + public void onSuccess() { + //调起第三方客户端 + // if (mAlertDialog != null && mAlertDialog.isShowing()) { + // mAlertDialog.cancel(); + // //mAlertDialog.dismiss(); + // } + } + + /** + * cancelLoading + */ + public void cancelLoading() { + if (mAlertDialog != null && mAlertDialog.isShowing()) { + mAlertDialog.cancel(); + //mAlertDialog.dismiss(); + } + } + + public static class ShareBuilder { + private Activity activity; + private int themeResId; + private String title; + private String summary; + private String content; + private String description; + private String url; + private int bitmapResID = R.mipmap.ic_share; + private long id; + private int type; + private String imageUrl; + + public ShareBuilder(Activity activity, int themeResId) { + this.activity = activity; + this.themeResId = themeResId; + } + + public ShareBuilder id(long id) { + this.id = id; + return this; + } + + public ShareBuilder type(int type) { + this.type = type; + return this; + } + + public ShareBuilder title(String title) { + this.title = title; + return this; + } + + public ShareBuilder content(String content) { + this.content = content; + this.summary = content; + this.description = content; + return this; + } + + public ShareBuilder url(String url) { + this.url = url; + return this; + } + + public ShareBuilder bitmapResID(int bitmapResID) { + this.bitmapResID = bitmapResID; + return this; + } + + public ShareDialogBuilder build() { + Share share = new Share(); + share.setTitle(title); + share.setSummary(summary); + share.setContent(content); + share.setDescription(description); + share.setUrl(url); + share.setBitmapResID(bitmapResID); + share.setImageUrl(imageUrl); + + share.setAppName("开源中国"); + share.setAppShareIcon(R.mipmap.ic_share); + + ShareDialogBuilder builder = new ShareDialogBuilder(activity, themeResId); + builder.mShare = share; + + if (id > 0 && type >= 0) { + About.Share aboutShare = About.buildShare(id, type); + aboutShare.title = title; + aboutShare.content = content; + builder.mAboutShare = aboutShare; + } + + builder.setView(R.layout.dialog_share_main); + return builder; + } + + public ShareBuilder imageUrl(String imageUrl) { + this.imageUrl = imageUrl; + return this; + } + } + + private class ShareActionAdapter extends RecyclerView.Adapter + implements View.OnClickListener { + private List mShareActions; + + ShareActionAdapter(List shareActions) { + this.mShareActions = shareActions; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + Context context = parent.getContext(); + View rootView = LayoutInflater.from(context).inflate(R.layout.dialog_share_item, + parent, false); + ViewHolder viewHolder = new ViewHolder(rootView); + viewHolder.mIvIcon.setOnClickListener(this); + return viewHolder; + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + ShareItem shareAction = mShareActions.get(position); + holder.itemView.setTag(shareAction); + holder.mIvIcon.setImageResource(shareAction.iconId); + holder.mTvName.setText(shareAction.nameId); + holder.mIvIcon.setTag(holder); + } + + @Override + public int getItemCount() { + return mShareActions == null ? 0 : mShareActions.size(); + } + + @Override + public void onClick(View v) { + try { + ViewHolder viewHolder = (ViewHolder) v.getTag(); + int position = viewHolder.getAdapterPosition(); + ShareItem item = (ShareItem) viewHolder.itemView.getTag(); + onItemClick(position, item); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + + static class ViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.share_icon) + ImageView mIvIcon; + @Bind(R.id.share_name) + TextView mTvName; + + public ViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } + + private static class ShareItem { + int iconId; + int nameId; + + ShareItem(int iconId, int nameId) { + this.iconId = iconId; + this.nameId = nameId; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/emoji/EmojiAdapter.java b/app/src/main/java/net/oschina/app/improve/emoji/EmojiAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..78cd5b6c930039b0d96d3eb0cf6580a9234d664c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/emoji/EmojiAdapter.java @@ -0,0 +1,44 @@ +package net.oschina.app.improve.emoji; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.ImageView; + +import net.oschina.app.R; +import net.oschina.app.emoji.Emojicon; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; + +/** + * Created by haibin + * on 2017/1/20. + */ + +class EmojiAdapter extends BaseRecyclerAdapter { + EmojiAdapter(Context context) { + super(context, NEITHER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new EmojiViewHolder(new ImageView(mContext)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Emojicon item, int position) { + int bound = (int) mContext.getResources().getDimension(R.dimen.space_49); + AbsListView.LayoutParams params = new AbsListView.LayoutParams(bound, bound); + holder.itemView.setLayoutParams(params); + int padding = (int) mContext.getResources().getDimension( + R.dimen.space_10); + holder.itemView.setPadding(padding, padding, padding, padding); + ((ImageView) holder.itemView).setImageResource(item.getResId()); + } + + private static class EmojiViewHolder extends RecyclerView.ViewHolder { + EmojiViewHolder(ImageView itemView) { + super(itemView); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/emoji/EmojiRecyclerView.java b/app/src/main/java/net/oschina/app/improve/emoji/EmojiRecyclerView.java new file mode 100644 index 0000000000000000000000000000000000000000..359ccde68d3977e3e2b3dd333dc4c58e2a49d006 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/emoji/EmojiRecyclerView.java @@ -0,0 +1,28 @@ +package net.oschina.app.improve.emoji; + +import android.content.Context; +import android.support.v4.view.ViewPager; +import android.view.MotionEvent; +import android.view.ViewParent; + +import net.oschina.app.improve.face.FaceRecyclerView; + +/** + * Created by haibin + * on 2017/1/20. + */ + +class EmojiRecyclerView extends FaceRecyclerView { + + EmojiRecyclerView(Context context, FaceRecyclerView.OnFaceClickListener listener) { + super(context, listener); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + ViewParent parent = this; + while (!((parent = parent.getParent()) instanceof ViewPager)) ; + parent.requestDisallowInterceptTouchEvent(true); + return super.dispatchTouchEvent(ev); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/emoji/EmojiView.java b/app/src/main/java/net/oschina/app/improve/emoji/EmojiView.java new file mode 100644 index 0000000000000000000000000000000000000000..23cb5f706b1acdd19483fdf8429041070dddd5e3 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/emoji/EmojiView.java @@ -0,0 +1,45 @@ +package net.oschina.app.improve.emoji; + +import android.content.Context; +import android.widget.EditText; + +import net.oschina.app.emoji.Emojicon; +import net.oschina.app.emoji.InputHelper; +import net.oschina.app.improve.face.FacePanelView; +import net.oschina.app.improve.face.FaceRecyclerView; +import net.oschina.app.util.TDevice; + +/** + * Created by haibin + * on 2016/11/10. + */ + +public class EmojiView extends FacePanelView { + private EditText mEditText; + + public EmojiView(Context context, EditText editText) { + super(context); + this.mEditText = editText; + setListener(new FacePanelListener() { + @Override + public void onDeleteClick() { + InputHelper.backspace(mEditText); + } + + @Override + public void hideSoftKeyboard() { + TDevice.hideSoftKeyboard(mEditText); + } + + @Override + public void onFaceClick(Emojicon v) { + InputHelper.input2OSC(mEditText, v); + } + }); + } + + @Override + protected FaceRecyclerView createRecyclerView() { + return new EmojiRecyclerView(getContext(), this); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/face/FacePanelView.java b/app/src/main/java/net/oschina/app/improve/face/FacePanelView.java new file mode 100644 index 0000000000000000000000000000000000000000..b0cfc7df4d6699995165734d0573b8b4b03ce588 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/face/FacePanelView.java @@ -0,0 +1,196 @@ +package net.oschina.app.improve.face; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +import net.oschina.app.R; +import net.oschina.app.emoji.DisplayRules; +import net.oschina.app.emoji.Emojicon; +import net.oschina.app.improve.utils.SoftKeyboardUtil; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ +public class FacePanelView extends LinearLayout implements View.OnClickListener, + FaceRecyclerView.OnFaceClickListener, SoftKeyboardUtil.IPanelHeightTarget { + private ViewPager mPager; + private FacePanelListener mListener; + private boolean mKeyboardShowing; + private AtomicBoolean mWillShowPanel = new AtomicBoolean(); + private int mRealHeight; + + public FacePanelView(Context context) { + super(context); + init(); + } + + public FacePanelView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public FacePanelView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public FacePanelView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + mRealHeight = SoftKeyboardUtil.getMinPanelHeight(getResources()); + setOrientation(VERTICAL); + inflate(getContext(), R.layout.lay_face_panel, this); + + mPager = (ViewPager) findViewById(R.id.view_pager); + findViewById(R.id.tv_qq).setOnClickListener(this); + findViewById(R.id.tv_emoji).setOnClickListener(this); + findViewById(R.id.btn_del).setOnClickListener(this); + + mPager.setAdapter(new PagerAdapter() { + @Override + public int getCount() { + return 2; + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + + @Override + public Object instantiateItem(ViewGroup container, final int position) { + final FaceRecyclerView view = createRecyclerView(); + bindRecyclerViewData(view, position); + container.addView(view); + return view; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + if (object instanceof FaceRecyclerView) { + container.removeView((FaceRecyclerView) object); + } + } + }); + + // init soft keyboard helper + SoftKeyboardUtil.attach((Activity) getContext(), this); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.tv_qq: + mPager.setCurrentItem(0); + break; + case R.id.tv_emoji: + mPager.setCurrentItem(1); + break; + case R.id.btn_del: + onDeleteClick(); + break; + } + } + + protected FaceRecyclerView createRecyclerView() { + return new FaceRecyclerView(getContext(), this); + } + + protected FaceRecyclerView bindRecyclerViewData(FaceRecyclerView view, final int position) { + view.setData(DisplayRules.getAllByType(position)); + return view; + } + + protected void onDeleteClick() { + FacePanelListener listener = mListener; + if (listener != null) + listener.onDeleteClick(); + } + + @Override + public void onFaceClick(Emojicon v) { + FacePanelListener listener = mListener; + if (listener != null) + listener.onFaceClick(v); + } + + public void setListener(FacePanelListener listener) { + mListener = listener; + } + + @Override + public void refreshHeight(int panelHeight) { + mRealHeight = panelHeight; + //UiUtil.changeViewHeight(this, panelHeight); + } + + @Override + public void onKeyboardShowing(boolean showing) { + mKeyboardShowing = showing; + if (showing) { + hidePanel(); + } else if (mWillShowPanel.getAndSet(false)) { + openPanel(); + } + } + + public boolean isShow() { + return getVisibility() != GONE; + } + + public void hidePanel() { + setVisibility(GONE); + } + + public void openPanel() { + if (mKeyboardShowing) { + mWillShowPanel.set(true); + FacePanelListener listener = mListener; + if (listener != null) + listener.hideSoftKeyboard(); + } else { + setVisibility(VISIBLE); + } + + } + + @Override + public int getPanelHeight() { + return mRealHeight; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mKeyboardShowing) { + heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, + MeasureSpec.getMode(MeasureSpec.EXACTLY)); + } else { + heightMeasureSpec = MeasureSpec.makeMeasureSpec(mRealHeight, + MeasureSpec.getMode(MeasureSpec.EXACTLY)); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + public interface FacePanelListener extends FaceRecyclerView.OnFaceClickListener { + void onDeleteClick(); + + void hideSoftKeyboard(); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/face/FaceRecyclerView.java b/app/src/main/java/net/oschina/app/improve/face/FaceRecyclerView.java new file mode 100644 index 0000000000000000000000000000000000000000..71ea03dee2a7ccf42f2f16e2db671109598e264f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/face/FaceRecyclerView.java @@ -0,0 +1,107 @@ +package net.oschina.app.improve.face; + +import android.content.Context; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; + +import net.oschina.app.R; +import net.oschina.app.emoji.Emojicon; +import net.oschina.app.util.TDevice; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ +public class FaceRecyclerView extends RecyclerView { + private FaceAdapter mAdapter; + private OnFaceClickListener mListener; + private final List mData = new ArrayList<>(); + + public FaceRecyclerView(Context context, OnFaceClickListener listener) { + super(context); + this.mListener = listener; + + // 自动计算显示的列数 + int autoCount = (int) (TDevice.getScreenWidth() / TDevice.dipToPx(getResources(), 48)); + + setLayoutManager(new GridLayoutManager(context, autoCount)); + setAdapter(mAdapter = new FaceAdapter()); + } + + public void setData(List icons) { + mData.addAll(icons); + mAdapter.notifyDataSetChanged(); + } + + private void onFaceClick(FaceViewHolder holder) { + OnFaceClickListener listener = mListener; + if (listener != null && holder.getTagData() != -1) { + Emojicon emojicon = mData.get(holder.getTagData()); + listener.onFaceClick(emojicon); + } + } + + private class FaceAdapter extends RecyclerView.Adapter { + + @Override + public FaceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View root = inflater.inflate(R.layout.lay_face_icon, parent, false); + final FaceViewHolder holder = new FaceViewHolder(root); + root.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onFaceClick(holder); + } + }); + return holder; + } + + @Override + public void onBindViewHolder(FaceViewHolder holder, int position) { + holder.bindData(mData.get(position), position); + } + + @Override + public int getItemCount() { + return mData.size(); + } + } + + private static class FaceViewHolder extends RecyclerView.ViewHolder { + private ImageView mImage; + + FaceViewHolder(View itemView) { + super(itemView); + mImage = (ImageView) itemView.findViewById(R.id.iv_face); + } + + void bindData(Emojicon emojicon, int position) { + itemView.setTag(R.id.recycle_tag, position); + Glide.with(itemView.getContext()) + .load(emojicon.getResId()) + .into(mImage); + } + + int getTagData() { + Object obj = itemView.getTag(R.id.recycle_tag); + if (obj != null && obj instanceof Integer) { + return (int) obj; + } + return -1; + } + } + + public interface OnFaceClickListener { + void onFaceClick(Emojicon v); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/api/API.java b/app/src/main/java/net/oschina/app/improve/git/api/API.java new file mode 100644 index 0000000000000000000000000000000000000000..967393193ceaab4ce6ab05abb7d9364fe082b80a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/api/API.java @@ -0,0 +1,210 @@ +package net.oschina.app.improve.git.api; + +import android.annotation.SuppressLint; +import android.text.TextUtils; + +import com.loopj.android.http.AsyncHttpClient; +import com.loopj.android.http.RequestParams; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.api.ApiHttpClient; +import net.oschina.app.improve.git.bean.Project; + +/** + * Created by haibin + * on 2017/3/9. + */ +@SuppressLint("DefaultLocale") +public final class API { + public static AsyncHttpClient mClient = new AsyncHttpClient(); + + static { + mClient.setURLEncodingEnabled(false); + } + + /** + * 获取码云推荐列表 + * + * @param page page=1、2、3 + * @param handler 回调 + */ + public static void getFeatureProjects(int page, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("page", page); + mClient.get("https://gitee.com/api/v3/projects/featured/osc", params, handler); + } + + /** + * 获取项目详情 + * + * @param id 项目id + * @param handler 回调 + */ + public static void getProjectDetail(long id, TextHttpResponseHandler handler) { + mClient.get(String.format("https://gitee.com/api/v3/projects/%d/osc", id), handler); + } + + /** + * 获取项目详情 + * + * @param pathWithNamespace 项目pathWithNamespace + * @param handler 回调 + */ + public static void getProjectDetail(String pathWithNamespace, TextHttpResponseHandler handler) { + mClient.get(String.format("https://gitee.com/api/v3/projects/%s/osc", pathWithNamespace), handler); + } + + /** + * 获取代码仓库 + * + * @param id 项目id + * @param path 仓库的相对路径 如:app/src/main + * @param refName 分支或者标签名称,默认为master分支 + * @param handler 回调 + */ + public static void getCodeTree(long id, String path, String refName, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("path", path); + params.put("ref_name", refName); + mClient.get(String.format("https://gitee.com/api/v3/projects/%d/repository/tree", id), params, handler); + } + + /** + * 获取代码详情 + * + * @param id 项目id + * @param filePath 仓库的相对路径 如:app/src/main + * @param ref 分支或者标签名称,默认为master分支 + * @param handler 回调 + */ + public static void getCodeDetail(long id, String filePath, String ref, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("file_path", filePath); + params.put("ref", ref); + mClient.get(String.format("https://gitee.com/api/v3/projects/%d/repository/files", id), params, handler); + } + + /** + * 获取项目分支 + * + * @param id 项目id + * @param handler 回调 + */ + public static void getProjectBranches(long id, TextHttpResponseHandler handler) { + mClient.get(String.format("https://gitee.com/api/v3/projects/%d/repository/branches", id), handler); + } + + /** + * 获取项目评论 + * + * @param id 项目id + * @param handler 回调 + */ + public static void getProjectComments(long id, String token, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("projectId", id); + params.put("pageToken", token); + ApiHttpClient.get("action/apiv2/git_comments_list", params, handler); + } + + /** + * 添加git项目评论 + * + * @param project 项目 + * @param comment 内容 + * @param handler 回调 + */ + public static void addProjectComment(Project project, String comment, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("projectId", project.getId()); + params.put("name", project.getName()); + params.put("pathWithNamespace", project.getPathWithNamespace()); + params.put("content", comment); + ApiHttpClient.post("action/apiv2/pub_git_comment", params, handler); + } + + /** + * 获取项目评论数 + * + * @param id 项目id + * @param handler 回调 + */ + public static void getProjectCommentCount(long id, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("projectId", id); + ApiHttpClient.get("action/apiv2/git_comments_count", params, handler); + } + + /** + * 获取代码片段 + * + * @param language language + * @param type type + * @param page page + * @param handler handler + */ + public static void getGists(String language, String type, int page, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + if (!TextUtils.isEmpty(language)) + params.put("language", language); + if (!TextUtils.isEmpty(type)) + params.put("type", type); + params.put("page", page); + mClient.get("https://gitee.com/api/v3/gists/public", params, handler); + } + + /** + * 获取代码片段详情 + * + * @param id 代码片段文件标识id + * @param handler 回调 + */ + public static void getGistDetail(String id, TextHttpResponseHandler handler) { + mClient.get(String.format("https://gitee.com/api/v3/gists/%s", id), handler); + } + + /** + * 发表代码片段评论 + * + * @param id id + * @param name name + * @param content content + * @param handler 回调 + */ + public static void pubGistComment(String id, String name, String url, String content, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("gistId", id); + params.put("name", name); + params.put("url", url); + params.put("content", content); + ApiHttpClient.post("action/apiv2/pub_gist_comment", params, handler); + } + + /** + * 代码片段评论列表 + * + * @param id id + * @param token token + * @param handler handler + */ + public static void getGistComments(String id, String token, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("gistId", id); + if (!TextUtils.isEmpty(token)) + params.put("pageToken", token); + ApiHttpClient.get("action/apiv2/gist_comments_list", params, handler); + } + + /** + * 获取代码片段评论数 + * + * @param id 项目id + * @param handler 回调 + */ + public static void getGistCommentCount(String id, TextHttpResponseHandler handler) { + RequestParams params = new RequestParams(); + params.put("gistId", id); + ApiHttpClient.get("action/apiv2/gist_comments_count", params, handler); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/git/bean/Branch.java b/app/src/main/java/net/oschina/app/improve/git/bean/Branch.java new file mode 100644 index 0000000000000000000000000000000000000000..03ab068f2b773de2186fdabf5f28c9cc9b3925c8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/bean/Branch.java @@ -0,0 +1,33 @@ +package net.oschina.app.improve.git.bean; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/** + * 分支 + * Created by haibin + * on 2016/12/9. + */ + +public class Branch implements Serializable { + private String name; + @SerializedName("protected") + private boolean isProtected; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isProtected() { + return isProtected; + } + + public void setProtected(boolean aProtected) { + isProtected = aProtected; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/bean/CodeDetail.java b/app/src/main/java/net/oschina/app/improve/git/bean/CodeDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..8f9ac537c5e3ac9f71348ab9f8770f0156fb02c8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/bean/CodeDetail.java @@ -0,0 +1,100 @@ +package net.oschina.app.improve.git.bean; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/** + * 代码详情 + * Created by haibin + * on 2016/12/9. + */ +@SuppressWarnings("unused") +public class CodeDetail implements Serializable { + + public static final String ENCODING_BASE64 = "base64"; + + @SerializedName("file_name") + private String fileName; + + @SerializedName("file_path") + private String filePath; + + private int size; + + private String encoding; + + private String content; + + private String ref; + + @SerializedName("blob_id") + private String blobId; + + @SerializedName("commit_id") + private String commitId; + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getRef() { + return ref; + } + + public void setRef(String ref) { + this.ref = ref; + } + + public String getBlobId() { + return blobId; + } + + public void setBlobId(String blobId) { + this.blobId = blobId; + } + + public String getCommitId() { + return commitId; + } + + public void setCommitId(String commitId) { + this.commitId = commitId; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/bean/Comment.java b/app/src/main/java/net/oschina/app/improve/git/bean/Comment.java new file mode 100644 index 0000000000000000000000000000000000000000..342550f3e3ac781d6b32d6ee8d2b7f15e5b319b2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/bean/Comment.java @@ -0,0 +1,58 @@ +package net.oschina.app.improve.git.bean; + +import net.oschina.app.improve.bean.simple.Author; + +import java.io.Serializable; + +/** + * Created by haibin + * on 2017/3/14. + */ + +public class Comment implements Serializable { + private int id; + private Author author; + private String content; + private String pubDate; + private int appClient; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getPubDate() { + return pubDate; + } + + public void setPubDate(String pubDate) { + this.pubDate = pubDate; + } + + public int getAppClient() { + return appClient; + } + + public void setAppClient(int appClient) { + this.appClient = appClient; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/bean/Gist.java b/app/src/main/java/net/oschina/app/improve/git/bean/Gist.java new file mode 100644 index 0000000000000000000000000000000000000000..56955230550aeb3de8da6b43a85226d8edb13165 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/bean/Gist.java @@ -0,0 +1,173 @@ +package net.oschina.app.improve.git.bean; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.Date; + +/** + * 代码片段 + * Created by haibin on 2017/5/10. + */ +@SuppressWarnings("unused") +public class Gist implements Serializable { + private String url; + private String id; + private User owner; + private String language; + private String summary; + private String category; + private String content; + private File[] files; + @SerializedName("comments_count") + private int commentCounts; + @SerializedName("stars_count") + private int startCounts; + @SerializedName("forks_count") + private int forkCounts; + @SerializedName("created_at") + private Date createdDate; + @SerializedName("updated_at") + private Date lastUpdateDate; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + + public User getOwner() { + return owner; + } + + public void setOwner(User owner) { + this.owner = owner; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public File[] getFiles() { + return files; + } + + public void setFiles(File[] files) { + this.files = files; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public int getCommentCounts() { + return commentCounts; + } + + public void setCommentCounts(int commentCounts) { + this.commentCounts = commentCounts; + } + + public int getStartCounts() { + return startCounts; + } + + public void setStartCounts(int startCounts) { + this.startCounts = startCounts; + } + + public int getForkCounts() { + return forkCounts; + } + + public void setForkCounts(int forkCounts) { + this.forkCounts = forkCounts; + } + + public Date getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Date createdDate) { + this.createdDate = createdDate; + } + + public Date getLastUpdateDate() { + return lastUpdateDate; + } + + public void setLastUpdateDate(Date lastUpdateDate) { + this.lastUpdateDate = lastUpdateDate; + } + + + public static class File implements Serializable { + + public static final int FILE_CODE = 1;//代码文件 + public static final int FILE_IMAGE = 2;//图片 + public static final int FILE_BIN = 3;//二进制文件,只能下载 + + private int type; + private String name; + private String content; + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/bean/NameSpace.java b/app/src/main/java/net/oschina/app/improve/git/bean/NameSpace.java new file mode 100644 index 0000000000000000000000000000000000000000..c5203302a261591ff250ca8c775cc4a6e0a809e3 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/bean/NameSpace.java @@ -0,0 +1,151 @@ +package net.oschina.app.improve.git.bean; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.Date; + +/** + * Created by haibin + * on 2016/11/4. + */ +@SuppressWarnings("unused") +public class NameSpace implements Serializable { + private String address; + private String avatar; + + @SerializedName("created_at") + private Date createdAt; + + private String description; + private String email; + + @SerializedName("enterprise_id") + private int enterpriseId; + private long id; + private int level; + private String location; + private String name; + + @SerializedName("owner_id") + private long ownerId; + + private String path; + + @SerializedName("updated_at") + private Date updatedDate; + + private String url; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public Date getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Date createdAt) { + this.createdAt = createdAt; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public int getEnterpriseId() { + return enterpriseId; + } + + public void setEnterpriseId(int enterpriseId) { + this.enterpriseId = enterpriseId; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public int getLevel() { + return level; + } + + public void setLevel(int level) { + this.level = level; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getOwnerId() { + return ownerId; + } + + public void setOwnerId(long ownerId) { + this.ownerId = ownerId; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public Date getUpdatedDate() { + return updatedDate; + } + + public void setUpdatedDate(Date updatedDate) { + this.updatedDate = updatedDate; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/bean/Project.java b/app/src/main/java/net/oschina/app/improve/git/bean/Project.java new file mode 100644 index 0000000000000000000000000000000000000000..a2be27e29a4c4675d9d56e393d4f98a5a06064b3 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/bean/Project.java @@ -0,0 +1,307 @@ +package net.oschina.app.improve.git.bean; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.Date; + +/** + * Created by haibin + * on 2016/11/4. + */ +@SuppressWarnings("unused") +public class Project implements Serializable { + private long id; + + private String name; + + private String description; + + private String img; + + @SerializedName("default_branch") + private String defaultBranch; + + private User owner; + + private String path; + + @SerializedName("path_with_namespace") + private String pathWithNamespace; + + @SerializedName("issues_enabled") + private boolean issuesEnabled; + + @SerializedName("pull_requests_enabled") + private boolean pullRequestsEnabled; + + @SerializedName("wiki_enabled") + private boolean wikiEnabled; + + @SerializedName("created_at") + private Date createdTime; + + private NameSpace namespace; + + @SerializedName("last_push_at") + private Date lastPushTime; + + private String language; + + @SerializedName("parent_id") + private Integer parentId; + + @SerializedName("forks_count") + private Integer forksCount; + + @SerializedName("stars_count") + private Integer starsCount; + + @SerializedName("watches_count") + private Integer watchesCount; + + private boolean stared; + + private boolean watched; + + private String relation; + + private int recomm; + + @SerializedName("real_path") + private String realPath; + + @SerializedName("svn_url_to_repo") + private String svnUrl; + + @SerializedName("issue_count") + private int issueCount; + @SerializedName("pull_request_count") + private int pullRequestCount; + + private String readme; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDefaultBranch() { + return defaultBranch; + } + + public void setDefaultBranch(String defaultBranch) { + this.defaultBranch = defaultBranch; + } + + public User getOwner() { + return owner; + } + + public void setOwner(User owner) { + this.owner = owner; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getPathWithNamespace() { + return pathWithNamespace; + } + + public void setPathWithNamespace(String pathWithNamespace) { + this.pathWithNamespace = pathWithNamespace; + } + + public boolean issuesEnabled() { + return issuesEnabled; + } + + public void setIssuesEnabled(boolean issuesEnabled) { + this.issuesEnabled = issuesEnabled; + } + + public boolean isPullRequestsEnabled() { + return pullRequestsEnabled; + } + + public void setPullRequestsEnabled(boolean pullRequestsEnabled) { + this.pullRequestsEnabled = pullRequestsEnabled; + } + + public boolean isWikiEnabled() { + return wikiEnabled; + } + + public void setWikiEnabled(boolean wikiEnabled) { + this.wikiEnabled = wikiEnabled; + } + + public Date getCreatedTime() { + return createdTime; + } + + public void setCreatedTime(Date createdTime) { + this.createdTime = createdTime; + } + + public NameSpace getNamespace() { + return namespace; + } + + public void setNamespace(NameSpace namespace) { + this.namespace = namespace; + } + + public Date getLastPushTime() { + return lastPushTime; + } + + public void setLastPushTime(Date lastPushTime) { + this.lastPushTime = lastPushTime; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public Integer getForksCount() { + return forksCount; + } + + public void setForksCount(Integer forksCount) { + this.forksCount = forksCount; + } + + public Integer getStarsCount() { + return starsCount; + } + + public void setStarsCount(Integer starsCount) { + this.starsCount = starsCount; + } + + public Integer getWatchesCount() { + return watchesCount; + } + + public void setWatchesCount(Integer watchesCount) { + this.watchesCount = watchesCount; + } + + public boolean isStared() { + return stared; + } + + public void setStared(boolean stared) { + this.stared = stared; + } + + public boolean isWatched() { + return watched; + } + + public void setWatched(boolean watched) { + this.watched = watched; + } + + public String getRelation() { + return relation; + } + + public void setRelation(String relation) { + this.relation = relation; + } + + public int getRecomm() { + return recomm; + } + + public void setRecomm(int recomm) { + this.recomm = recomm; + } + + public String getRealPath() { + return realPath; + } + + public void setRealPath(String realPath) { + this.realPath = realPath; + } + + public String getSvnUrl() { + return svnUrl; + } + + public void setSvnUrl(String svnUrl) { + this.svnUrl = svnUrl; + } + + public String getImg() { + return img; + } + + public void setImg(String img) { + this.img = img; + } + + public String getReadme() { + return readme; + } + + public void setReadme(String readme) { + this.readme = readme; + } + + public int getIssueCount() { + return issueCount; + } + + public void setIssueCount(int issueCount) { + this.issueCount = issueCount; + } + + public int getPullRequestCount() { + return pullRequestCount; + } + + public void setPullRequestCount(int pullRequestCount) { + this.pullRequestCount = pullRequestCount; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/bean/Tree.java b/app/src/main/java/net/oschina/app/improve/git/bean/Tree.java new file mode 100644 index 0000000000000000000000000000000000000000..b7639efab1318ecaaf3e5c5c7477467340c537e8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/bean/Tree.java @@ -0,0 +1,56 @@ +package net.oschina.app.improve.git.bean; + +import java.io.Serializable; + +/** + * 代码仓库 + * Created by haibin + * on 2016/12/9. + */ +@SuppressWarnings("unused") +public class Tree implements Serializable { + + private static final String CODE_TYPE_FOLDER = "tree";//文件夹 + private static final String CODE_TYPE_FILE = "blob";//文件 + + private String name; + private String type; + private String id; + private String mode; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getMode() { + return mode; + } + + public void setMode(String mode) { + this.mode = mode; + } + + public boolean isFile() { + return CODE_TYPE_FILE.equals(type); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/bean/User.java b/app/src/main/java/net/oschina/app/improve/git/bean/User.java new file mode 100644 index 0000000000000000000000000000000000000000..a7758709a839225142173ff5ced0ce7bd4ff2e16 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/bean/User.java @@ -0,0 +1,192 @@ +package net.oschina.app.improve.git.bean; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/** + * Created by haibin + * on 2016/11/4. + */ +@SuppressWarnings("unused") +public class User implements Serializable { + + private long id; + + private String username; + + private String name; + + private String bio; + + @SerializedName("weibo") + private String weiBo; + + private String blog; + + @SerializedName("theme_id") + private int themeId; + + private String state; + + @SerializedName("created_at") + private String createdData; + + private String portrait; + + private String email; + + @SerializedName("new_portrait") + private String newPortrait; + + @SerializedName("private_token") + private String privateToken; + + @SerializedName("is_admin") + private boolean isAdmin; + + @SerializedName("can_create_group") + private boolean canCreateGroup; + + @SerializedName("can_create_project") + private boolean canCreateProject; + + @SerializedName("can_create_team") + private boolean canCreateTeam; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getBio() { + return bio; + } + + public void setBio(String bio) { + this.bio = bio; + } + + public String getWeiBo() { + return weiBo; + } + + public void setWeiBo(String weiBo) { + this.weiBo = weiBo; + } + + public String getBlog() { + return blog; + } + + public void setBlog(String blog) { + this.blog = blog; + } + + public int getThemeId() { + return themeId; + } + + public void setThemeId(int themeId) { + this.themeId = themeId; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getCreatedData() { + return createdData; + } + + public void setCreatedData(String createdData) { + this.createdData = createdData; + } + + public String getPortrait() { + return portrait; + } + + public void setPortrait(String portrait) { + this.portrait = portrait; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getNewPortrait() { + return newPortrait; + } + + public void setNewPortrait(String newPortrait) { + this.newPortrait = newPortrait; + } + + public String getPrivateToken() { + return privateToken; + } + + public void setPrivateToken(String privateToken) { + this.privateToken = privateToken; + } + + public boolean isAdmin() { + return isAdmin; + } + + public void setAdmin(boolean admin) { + isAdmin = admin; + } + + public boolean isCanCreateGroup() { + return canCreateGroup; + } + + public void setCanCreateGroup(boolean canCreateGroup) { + this.canCreateGroup = canCreateGroup; + } + + public boolean isCanCreateProject() { + return canCreateProject; + } + + public void setCanCreateProject(boolean canCreateProject) { + this.canCreateProject = canCreateProject; + } + + public boolean isCanCreateTeam() { + return canCreateTeam; + } + + public void setCanCreateTeam(boolean canCreateTeam) { + this.canCreateTeam = canCreateTeam; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/branch/BranchAdapter.java b/app/src/main/java/net/oschina/app/improve/git/branch/BranchAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..a06d6c5feb3b3d300ee83387aad7828637bba8ad --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/branch/BranchAdapter.java @@ -0,0 +1,41 @@ +package net.oschina.app.improve.git.branch; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.git.bean.Branch; + +/** + * Created by haibin + * on 2017/3/13. + */ + +class BranchAdapter extends BaseRecyclerAdapter { + BranchAdapter(Context context) { + super(context, NEITHER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new BranchViewHolder(mInflater.inflate(R.layout.item_list_branch, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Branch item, int position) { + ((BranchViewHolder) holder).mTextBranch.setText(item.getName()); + } + + private static class BranchViewHolder extends RecyclerView.ViewHolder { + TextView mTextBranch; + + BranchViewHolder(View itemView) { + super(itemView); + mTextBranch = (TextView) itemView.findViewById(R.id.tv_branch); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/branch/BranchContract.java b/app/src/main/java/net/oschina/app/improve/git/branch/BranchContract.java new file mode 100644 index 0000000000000000000000000000000000000000..f2396e3364dfd9ac4a33d7cb74cdaa00338118e4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/branch/BranchContract.java @@ -0,0 +1,25 @@ +package net.oschina.app.improve.git.branch; + +import net.oschina.app.improve.base.BasePresenter; +import net.oschina.app.improve.base.BaseView; +import net.oschina.app.improve.git.bean.Branch; + +import java.util.List; + +/** + * Created by haibin + * on 2017/3/13. + */ + + interface BranchContract { + + interface View extends BaseView { + void showGetBranchSuccess(List branches); + + void showGetBranchFailure(int strId); + } + + interface Presenter extends BasePresenter { + void getBranches(long id); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/branch/BranchPopupWindow.java b/app/src/main/java/net/oschina/app/improve/git/branch/BranchPopupWindow.java new file mode 100644 index 0000000000000000000000000000000000000000..e9f90764699b683ddb393123c676dba24e36f91c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/branch/BranchPopupWindow.java @@ -0,0 +1,125 @@ +package net.oschina.app.improve.git.branch; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.os.Build; +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 android.widget.PopupWindow; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.git.bean.Branch; + +import java.util.List; + +/** + * Created by haibin + * on 2017/3/13. + */ +@SuppressWarnings("unused") +public class BranchPopupWindow extends PopupWindow implements + View.OnAttachStateChangeListener, + BaseRecyclerAdapter.OnItemClickListener, + BranchContract.View { + private BranchAdapter mAdapter; + private Callback mCallback; + private long mProjectId; + private BranchContract.Presenter mPresenter; + + @SuppressLint("InflateParams") + public BranchPopupWindow(Context context, long mProjectId, Callback callback) { + super(LayoutInflater.from(context).inflate(R.layout.popup_window_branch, null), + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + mCallback = callback; + this.mProjectId = mProjectId; + + setAnimationStyle(R.style.popup_anim_style_alpha); + setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + setOutsideTouchable(true); + setFocusable(true); + + View content = getContentView(); + content.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dismiss(); + } + }); + content.addOnAttachStateChangeListener(this); + RecyclerView mRecyclerView = (RecyclerView) content.findViewById(R.id.rv_branch); + mAdapter = new BranchAdapter(context); + mRecyclerView.setLayoutManager(new LinearLayoutManager(context)); + mRecyclerView.setAdapter(mAdapter); + mAdapter.setOnItemClickListener(this); + mPresenter = new BranchPresenter(this); + mPresenter.getBranches(mProjectId); + } + + @Override + public void setPresenter(BranchContract.Presenter presenter) { + + } + + @Override + public void showNetworkError(int strId) { + + } + + @Override + public void showGetBranchSuccess(List branches) { + mAdapter.resetItem(branches); + } + + @Override + public void showGetBranchFailure(int strId) { + + } + + @Override + public void showAsDropDown(View anchor) { + if(Build.VERSION.SDK_INT >= 24){ + Rect visibleFrame = new Rect(); + anchor.getGlobalVisibleRect(visibleFrame); + int height = anchor.getResources().getDisplayMetrics().heightPixels - visibleFrame.bottom; + setHeight(height); + } + + super.showAsDropDown(anchor); + } + + @Override + public void onViewAttachedToWindow(View v) { + if (mAdapter.getCount() == 0) + mPresenter.getBranches(mProjectId); + if (mCallback != null) + mCallback.onShow(); + } + + @Override + public void onViewDetachedFromWindow(View v) { + if (mCallback != null) + mCallback.onDismiss(); + } + + @Override + public void onItemClick(int position, long itemId) { + if (mCallback != null) + mCallback.onSelect(mAdapter.getItem(position)); + dismiss(); + } + + public interface Callback { + void onSelect(Branch branch); + + void onDismiss(); + + void onShow(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/branch/BranchPresenter.java b/app/src/main/java/net/oschina/app/improve/git/branch/BranchPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..fafe2dbd683e7ede74e937aae94b7e5a99c238b7 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/branch/BranchPresenter.java @@ -0,0 +1,55 @@ +package net.oschina.app.improve.git.branch; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.improve.git.api.API; +import net.oschina.app.improve.git.bean.Branch; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2017/3/13. + */ + +class BranchPresenter implements BranchContract.Presenter { + private final BranchContract.View mView; + + BranchPresenter(BranchContract.View mView) { + this.mView = mView; + this.mView.setPresenter(this); + } + + @Override + public void getBranches(long id) { + API.getProjectBranches(id, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showGetBranchFailure(R.string.state_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + List bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.size() != 0) { + mView.showGetBranchSuccess(bean); + } else { + mView.showGetBranchFailure(R.string.state_network_error); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.state_network_error); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/code/CodeDetailActivity.java b/app/src/main/java/net/oschina/app/improve/git/code/CodeDetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..5cc8180cb8fec093e317ab8bb38b55675540a22f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/code/CodeDetailActivity.java @@ -0,0 +1,122 @@ +package net.oschina.app.improve.git.code; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.support.v7.app.ActionBar; +import android.text.TextUtils; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.dialog.ShareDialog; +import net.oschina.app.improve.git.bean.Project; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.StringUtils; + +/** + * Created by haibin + * on 2017/3/13. + */ + +public class CodeDetailActivity extends BackActivity { + private ShareDialog mAlertDialog; + private Project mProject; + private CodeDetailPresenter mPresenter; + + public static void show(Context context, Project project, String path, String branch) { + Intent intent = new Intent(context, CodeDetailActivity.class); + Log.e("path", path); + intent.putExtra("path", path); + intent.putExtra("branch", branch); + intent.putExtra("project", project); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_code_detail; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + Intent intent = getIntent(); + CodeDetailFragment fragment = CodeDetailFragment.newInstance(intent.getStringExtra("path")); + addFragment(R.id.fl_content, fragment); + mProject = (Project) intent.getSerializableExtra("project"); + mPresenter = new CodeDetailPresenter(fragment, mProject, + intent.getStringExtra("path"), + intent.getStringExtra("branch")); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_code_detail, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + toShare(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onResume() { + super.onResume(); + if (mAlertDialog != null) { + mAlertDialog.dismiss(); + } + } + + private void toShare() { + String content = mProject.getDescription().trim(); + if (content.length() > 55) { + content = HTMLUtil.delHTMLTag(content); + if (content.length() > 55) + content = StringUtils.getSubString(0, 55, content); + } else { + content = HTMLUtil.delHTMLTag(content); + } + if (TextUtils.isEmpty(content)) + content = ""; + + // 分享 + if (mAlertDialog == null) { + mAlertDialog = new + ShareDialog(this) + .title(mProject.getOwner().getName() + "/" + mProject.getName()) + .content(content) + .url(mPresenter.getShareUrl()) + .bitmapResID(R.mipmap.ic_git) + .with(); + } + mAlertDialog.show(); + + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + ActionBar bar = getSupportActionBar(); + if(bar != null) + bar.hide(); + mPresenter.changeConfig(true); + } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + mPresenter.changeConfig(false); + ActionBar bar = getSupportActionBar(); + if(bar != null) + bar.show(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/code/CodeDetailContract.java b/app/src/main/java/net/oschina/app/improve/git/code/CodeDetailContract.java new file mode 100644 index 0000000000000000000000000000000000000000..7bd6a0f7a5e77e708ac5f7126baaa7991a5e0d22 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/code/CodeDetailContract.java @@ -0,0 +1,41 @@ +package net.oschina.app.improve.git.code; + +import net.oschina.app.improve.base.BasePresenter; +import net.oschina.app.improve.base.BaseView; +import net.oschina.app.improve.git.bean.CodeDetail; + +/** + * Created by haibin + * on 2017/3/13. + */ + + interface CodeDetailContract { + + interface View extends BaseView { + void showGetCodeSuccess(CodeDetail detail); + + void showGetCodeFailure(int strId); + + /** + * 显示横屏 + */ + void showLandscape(); + + /** + * 显示竖屏 + */ + void showPortrait(); + } + + interface Presenter extends BasePresenter { + void getCodeDetail(); + + String getShareUrl(); + + /** + * 改变配置 + * @param isLandscape 是否是横屏 + */ + void changeConfig(boolean isLandscape); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/code/CodeDetailFragment.java b/app/src/main/java/net/oschina/app/improve/git/code/CodeDetailFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..225fde49a4c188c1b2855d06b22b9236dc76fdf6 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/code/CodeDetailFragment.java @@ -0,0 +1,99 @@ +package net.oschina.app.improve.git.code; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.View; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.git.bean.CodeDetail; +import net.oschina.app.improve.git.utils.MarkdownUtils; +import net.oschina.app.improve.git.utils.SourceEditor; + +import butterknife.Bind; + +/** + * Created by haibin + * on 2017/3/13. + */ + +public class CodeDetailFragment extends BaseFragment implements CodeDetailContract.View { + + @Bind(R.id.tv_file_name) + TextView mTextFileName; + @Bind(R.id.webView) + WebView mWebView; + @Bind(R.id.ll_name) + LinearLayout mLinearName; + @Bind(R.id.line) + View mLine; + private SourceEditor mEditor; + private CodeDetailContract.Presenter mPresenter; + + static CodeDetailFragment newInstance(String fileName) { + CodeDetailFragment fragment = new CodeDetailFragment(); + Bundle bundle = new Bundle(); + bundle.putString("fileName", fileName); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_code_detail; + } + + @SuppressLint("SetJavaScriptEnabled") + @Override + protected void initData() { + super.initData(); + WebSettings settings = mWebView.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setDefaultFontSize(10); + settings.setAllowContentAccess(true); + mWebView.setWebChromeClient(new WebChromeClient() { + }); + mEditor = new SourceEditor(mWebView); + mTextFileName.setText(getArguments().getString("fileName")); + mPresenter.getCodeDetail(); + } + + @Override + public void showNetworkError(int strId) { + + } + + @Override + public void showGetCodeSuccess(CodeDetail detail) { + String pathName = getArguments().getString("fileName"); + mEditor.setMarkdown(MarkdownUtils.isMarkdown(pathName)); + mEditor.setSource(pathName, detail); + } + + @Override + public void showGetCodeFailure(int strId) { + + } + + @Override + public void showLandscape() { + mLine.setVisibility(View.GONE); + mLinearName.setVisibility(View.GONE); + } + + @Override + public void showPortrait() { + mLine.setVisibility(View.VISIBLE); + mLinearName.setVisibility(View.VISIBLE); + } + + @Override + public void setPresenter(CodeDetailContract.Presenter presenter) { + this.mPresenter = presenter; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/code/CodeDetailPresenter.java b/app/src/main/java/net/oschina/app/improve/git/code/CodeDetailPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..a069ab0d6f09a4a121594992668b48b141881e73 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/code/CodeDetailPresenter.java @@ -0,0 +1,81 @@ +package net.oschina.app.improve.git.code; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.improve.git.api.API; +import net.oschina.app.improve.git.bean.CodeDetail; +import net.oschina.app.improve.git.bean.Project; + +import java.lang.reflect.Type; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2017/3/13. + */ + +class CodeDetailPresenter implements CodeDetailContract.Presenter { + private final CodeDetailContract.View mView; + private String mFileName; + private Project mProject; + private String mBranch; + + CodeDetailPresenter(CodeDetailContract.View mView, + Project project, + String mFileName, + String mBranch) { + this.mView = mView; + this.mProject = project; + this.mFileName = mFileName; + this.mBranch = mBranch; + this.mView.setPresenter(this); + } + + @Override + public void getCodeDetail() { + API.getCodeDetail(mProject.getId(), mFileName, mBranch, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken() { + }.getType(); + CodeDetail bean = new Gson().fromJson(responseString, type); + if (bean != null) { + mView.showGetCodeSuccess(bean); + } else { + mView.showGetCodeFailure(R.string.get_code_failure); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.state_network_error); + } + } + }); + } + + @Override + public void changeConfig(boolean isLandscape) { + if (isLandscape) + mView.showLandscape(); + else + mView.showPortrait(); + } + + + @Override + public String getShareUrl() { + return String.format("https://gitee.com/%s/blob/%s/%s", + mProject.getPathWithNamespace(), + mBranch, + mFileName); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/comment/CommentActivity.java b/app/src/main/java/net/oschina/app/improve/git/comment/CommentActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..bfccf557cab3f8eef551b52b0610ea3747369a97 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/comment/CommentActivity.java @@ -0,0 +1,123 @@ +package net.oschina.app.improve.git.comment; + +import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.View; +import android.widget.LinearLayout; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.git.bean.Comment; +import net.oschina.app.improve.git.bean.Project; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; + +/** + * Created by haibin + * on 2017/3/14. + */ + +public class CommentActivity extends BackActivity implements CommentContract.Action, View.OnClickListener { + private CommentPresenter mPresenter; + protected CommentBar mDelegation; + private String mMentionStr = ""; + protected boolean mInputDoubleEmpty = false; + + public static void show(Context context, Project project) { + Intent intent = new Intent(context, CommentActivity.class); + intent.putExtra("project", project); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_git_comment; + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + CommentFragment fragment = CommentFragment.newInstance(); + final Project project = (Project) getIntent() + .getExtras() + .getSerializable("project"); + addFragment(R.id.fl_content, fragment); + mPresenter = new CommentPresenter(fragment, this, project); + LinearLayout layComment = (LinearLayout) findViewById(R.id.ll_comment); + mDelegation = CommentBar.delegation(this, layComment); + mDelegation.hideFav(); + mDelegation.hideCommentCount(); + mDelegation.getBottomSheet().hideSyncAction(); + mDelegation.getBottomSheet().hideMentionAction(); + mDelegation.getBottomSheet().setMentionListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if ((AccountHelper.isLogin())) { + UserSelectFriendsActivity.show(CommentActivity.this, mDelegation.getBottomSheet().getEditText()); + } else { + LoginActivity.show(CommentActivity.this, 1); + } + } + }); + mDelegation.getBottomSheet().getEditText().setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL) { + handleKeyDel(); + } + return false; + } + }); + mDelegation.getBottomSheet().setCommitListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mPresenter.addComment(mMentionStr + mDelegation.getBottomSheet().getCommentText()); + } + }); + } + + @Override + public void onClick(View v) { + Comment comment = (Comment) v.getTag(); + Author author = comment.getAuthor(); + mMentionStr = "回复 @" + (author == null ? "匿名" : author.getName()) + ":"; + mDelegation.getBottomSheet().show(mMentionStr); + } + + @Override + public void showAddCommentSuccess(Comment comment, int strId) { + mDelegation.getBottomSheet().getEditText().setText(""); + mDelegation.getBottomSheet().getEditText().setHint("发表评论"); + mMentionStr = ""; + mDelegation.getBottomSheet().dismiss(); + } + + @Override + public void showAddCommentFailure(int strId) { + + } + + protected void handleKeyDel() { + if (!TextUtils.isEmpty(mMentionStr)) { + if (TextUtils.isEmpty(mDelegation.getBottomSheet().getCommentText())) { + if (mInputDoubleEmpty) { + mMentionStr = ""; + mDelegation.setCommentHint("发表评论"); + mDelegation.getBottomSheet().getEditText().setHint("发表评论"); + } else { + mInputDoubleEmpty = true; + } + } else { + mInputDoubleEmpty = false; + } + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/comment/CommentAdapter.java b/app/src/main/java/net/oschina/app/improve/git/comment/CommentAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..6b23864b270cfefe05552950ec50b24a0cd0ba31 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/comment/CommentAdapter.java @@ -0,0 +1,78 @@ +package net.oschina.app.improve.git.comment; + +import android.annotation.SuppressLint; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.git.bean.Comment; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; +import net.oschina.app.widget.TweetTextView; + +/** + * Created by haibin + * on 2017/3/14. + */ + +public class CommentAdapter extends BaseGeneralRecyclerAdapter { + private View.OnClickListener mCommentListener; + + public CommentAdapter(Callback callback) { + super(callback, ONLY_FOOTER); + } + + void setCommentListener(View.OnClickListener mCommentListener) { + this.mCommentListener = mCommentListener; + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new CommentViewHolder(mInflater.inflate(R.layout.item_list_git_comment, parent, false)); + } + + @SuppressLint("SetTextI18n") + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Comment item, int position) { + CommentViewHolder h = (CommentViewHolder) holder; + final Author author = item.getAuthor(); + h.mIdentityView.setup(author); + h.mImageOwner.setup(author); + h.mImageOwner.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + OtherUserHomeActivity.show(mContext, author); + } + }); + h.mTextName.setText(author == null ? "火星网友" : author.getName()); + h.mTextComment.setText(item.getContent()); + h.mTextPubDate.setText(StringUtils.formatSomeAgo(item.getPubDate())); + h.mImageComment.setTag(item); + h.mImageComment.setOnClickListener(mCommentListener); + } + + private static class CommentViewHolder extends RecyclerView.ViewHolder { + PortraitView mImageOwner; + IdentityView mIdentityView; + TextView mTextName, mTextPubDate; + TweetTextView mTextComment; + ImageView mImageComment; + + CommentViewHolder(View itemView) { + super(itemView); + mImageOwner = (PortraitView) itemView.findViewById(R.id.civ_owner); + mIdentityView = (IdentityView) itemView.findViewById(R.id.identityView); + mTextName = (TextView) itemView.findViewById(R.id.tv_name); + mTextPubDate = (TextView) itemView.findViewById(R.id.tv_pub_date); + mTextComment = (TweetTextView) itemView.findViewById(R.id.tv_comment); + mImageComment = (ImageView) itemView.findViewById(R.id.btn_comment); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/comment/CommentContract.java b/app/src/main/java/net/oschina/app/improve/git/comment/CommentContract.java new file mode 100644 index 0000000000000000000000000000000000000000..093e331722f1569e9be215b28535501d0ff550b8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/comment/CommentContract.java @@ -0,0 +1,29 @@ +package net.oschina.app.improve.git.comment; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.git.bean.Comment; + +/** + * Created by haibin + * on 2017/3/14. + */ + +interface CommentContract { + + interface Action { + void showAddCommentSuccess(Comment comment, int strId); + + void showAddCommentFailure(int strId); + } + + interface View extends BaseListView { + void showAddCommentSuccess(Comment comment, int strId); + + void showAddCommentFailure(int strId); + } + + interface Presenter extends BaseListPresenter { + void addComment(String content); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/comment/CommentFragment.java b/app/src/main/java/net/oschina/app/improve/git/comment/CommentFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..b9b0fef1efb5d761aa0037822446e0dc202258ab --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/comment/CommentFragment.java @@ -0,0 +1,47 @@ +package net.oschina.app.improve.git.comment; + +import android.view.View; + +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.git.bean.Comment; +import net.oschina.app.improve.widget.SimplexToast; + +/** + * Created by haibin + * on 2017/3/14. + */ + +public class CommentFragment extends BaseRecyclerFragment + implements CommentContract.View { + + static CommentFragment newInstance() { + return new CommentFragment(); + } + + @Override + protected void initData() { + super.initData(); + ((CommentAdapter) mAdapter).setCommentListener((View.OnClickListener) getContext()); + } + + @Override + protected void onItemClick(Comment comment, int position) { + + } + + @Override + public void showAddCommentSuccess(Comment comment, int strId) { + mAdapter.addItem(comment); + } + + @Override + public void showAddCommentFailure(int strId) { + SimplexToast.show(mContext, strId); + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new CommentAdapter(this); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/comment/CommentPresenter.java b/app/src/main/java/net/oschina/app/improve/git/comment/CommentPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..9ebcefb12c18855a3b049c6027a974fd3f7c0392 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/comment/CommentPresenter.java @@ -0,0 +1,140 @@ +package net.oschina.app.improve.git.comment; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.git.api.API; +import net.oschina.app.improve.git.bean.Comment; +import net.oschina.app.improve.git.bean.Project; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2017/3/14. + */ + +class CommentPresenter implements CommentContract.Presenter { + private final CommentContract.View mView; + private final CommentContract.Action mAction; + private final Project mProject; + private String mToken; + + CommentPresenter(CommentContract.View mView, CommentContract.Action mAction, Project mProject) { + this.mView = mView; + this.mProject = mProject; + this.mAction = mAction; + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + API.getProjectComments(mProject.getId(), "null", new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + mToken = bean.getResult().getNextPageToken(); + List list = bean.getResult().getItems(); + if (list != null) { + mView.onRefreshSuccess(list); + if (list.size() < 20) { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + API.getProjectComments(mProject.getId(), mToken, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + mToken = bean.getResult().getNextPageToken(); + List list = bean.getResult().getItems(); + if (list != null && list.size() != 0) { + mView.onLoadMoreSuccess(list); + } else { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void addComment(String content) { + API.addProjectComment(mProject, content, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showAddCommentFailure(R.string.pub_comment_failed); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + mAction.showAddCommentSuccess(bean.getResult(), R.string.pub_comment_success); + mView.showAddCommentSuccess(bean.getResult(), R.string.pub_comment_success); + } else { + mAction.showAddCommentFailure(R.string.pub_comment_failed); + mView.showAddCommentFailure(R.string.pub_comment_failed); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showAddCommentFailure(R.string.pub_comment_failed); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/detail/ProjectDetailActivity.java b/app/src/main/java/net/oschina/app/improve/git/detail/ProjectDetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..ef6060b81316f89fd4686094d02a205b5c2f664b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/detail/ProjectDetailActivity.java @@ -0,0 +1,123 @@ +package net.oschina.app.improve.git.detail; + +import android.content.Context; +import android.content.Intent; +import android.view.View; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.git.api.API; +import net.oschina.app.improve.git.bean.Project; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2017/3/9. + */ + +public class ProjectDetailActivity extends BackActivity implements ProjectDetailContract.EmptyView { + + @Bind(R.id.emptyLayout) + EmptyLayout mEmptyLayout; + + public static void show(final Context context, final Project project) { + Intent intent = new Intent(context, ProjectDetailActivity.class); + intent.putExtra("project", project); + context.startActivity(intent); + } + + public static void show(final Context context, final String pathWithNamespace, final String name, final String url) { + API.getProjectDetail(pathWithNamespace + "%2F" + name, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (TDevice.hasInternet()) { + UIHelper.openExternalBrowser(context, url); + } else { + show(context, pathWithNamespace, name); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + show(context, pathWithNamespace, name); + } else { + UIHelper.openExternalBrowser(context, url); + } + } catch (Exception e) { + e.printStackTrace(); + UIHelper.openExternalBrowser(context, url); + } + } + }); + } + + public static void show(Context context, String pathWithNamespace, String name) { + Intent intent = new Intent(context, ProjectDetailActivity.class); + Project project = new Project(); + project.setName(name); + project.setPathWithNamespace(pathWithNamespace); + intent.putExtra("project", project); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_project_detail; + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + final Project project = (Project) getIntent() + .getExtras() + .getSerializable("project"); + ProjectDetailFragment fragment = ProjectDetailFragment.newInstance(project); + final ProjectDetailContract.Presenter presenter = new ProjectDetailPresenter(fragment, this, project); + mEmptyLayout.setOnLayoutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mEmptyLayout.getErrorState() != EmptyLayout.NETWORK_LOADING) { + mEmptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + assert project != null; + if (project.getId() == 0) { + presenter.getProjectDetail(project.getName(), project.getPathWithNamespace()); + } else { + presenter.getProjectDetail(project.getId()); + } + } + } + }); + + addFragment(R.id.fl_content, fragment); + } + + @Override + public void showGetDetailSuccess(int strId) { + mEmptyLayout.setErrorType(strId); + } + + @Override + public void showGetDetailFailure(int strId) { + mEmptyLayout.setErrorType(strId); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/detail/ProjectDetailContract.java b/app/src/main/java/net/oschina/app/improve/git/detail/ProjectDetailContract.java new file mode 100644 index 0000000000000000000000000000000000000000..65923e34a432876cc436704eae09b07043083339 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/detail/ProjectDetailContract.java @@ -0,0 +1,37 @@ +package net.oschina.app.improve.git.detail; + +import net.oschina.app.improve.base.BasePresenter; +import net.oschina.app.improve.base.BaseView; +import net.oschina.app.improve.git.bean.Project; + +/** + * Created by haibin + * on 2017/3/9. + */ +interface ProjectDetailContract { + + interface EmptyView { + void showGetDetailSuccess(int strId); + + void showGetDetailFailure(int strId); + } + + @SuppressWarnings("unused") + interface View extends BaseView { + void showGetDetailSuccess(Project project, int strId); + + void showGetDetailFailure(int strId); + + void showGetCommentCountSuccess(int count); + } + + interface Presenter extends BasePresenter { + void getProjectDetail(long id); + + void getProjectDetail(String name, String pathWithNamespace); + + String getShareUrl(); + + void getCommentCount(long id); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/detail/ProjectDetailFragment.java b/app/src/main/java/net/oschina/app/improve/git/detail/ProjectDetailFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..dcbe0940f00d23ffae4104b4838c9dc961721041 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/detail/ProjectDetailFragment.java @@ -0,0 +1,200 @@ +package net.oschina.app.improve.git.detail; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.dialog.ShareDialog; +import net.oschina.app.improve.git.bean.Project; +import net.oschina.app.improve.git.bean.User; +import net.oschina.app.improve.git.comment.CommentActivity; +import net.oschina.app.improve.git.tree.TreeActivity; +import net.oschina.app.improve.widget.OWebView; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.StringUtils; + +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * Created by haibin + * on 2017/3/10. + */ +@SuppressWarnings("unused") +public class ProjectDetailFragment extends BaseFragment implements ProjectDetailContract.View, + View.OnClickListener { + @Bind(R.id.webView) + OWebView mWebView; + @Bind(R.id.tv_name) + TextView mTextName; + @Bind(R.id.tv_language) + TextView mTextLanguage; + @Bind(R.id.tv_update_date) + TextView mTextUpdateDate; + @Bind(R.id.tv_start_count) + TextView mTextStarCount; + @Bind(R.id.tv_watches_count) + TextView mTextWatchCount; + @Bind(R.id.tv_fork_count) + TextView mTextForkCount; + @Bind(R.id.tv_description) + TextView mTextDescription; + @Bind(R.id.tv_issues_count) + TextView mTextIssuesCount; + @Bind(R.id.tv_pr_count) + TextView mTexPrCount; + @Bind(R.id.tv_comment_count) + TextView mTextCommentCount; + + private ProjectDetailContract.Presenter mPresenter; + private Project mProject; + private ShareDialog mAlertDialog; + + public static ProjectDetailFragment newInstance(Project project) { + ProjectDetailFragment fragment = new ProjectDetailFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable("project", project); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_project_detail; + } + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + mProject = (Project) bundle.getSerializable("project"); + } + + @Override + protected void initData() { + super.initData(); + if(mPresenter == null) + return; + initProject(mProject); + if (mProject.getId() == 0) { + mPresenter.getProjectDetail(mProject.getName(), mProject.getPathWithNamespace()); + } else { + mPresenter.getProjectDetail(mProject.getId()); + } + } + + @OnClick({R.id.ll_code, R.id.ll_share, R.id.ll_comment}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.ll_code: + TreeActivity.show(mContext, mProject); + break; + case R.id.ll_share: + toShare(); + break; + case R.id.ll_comment: + CommentActivity.show(mContext, mProject); + break; + } + } + + @Override + public void showNetworkError(int strId) { + + } + + @Override + public void showGetCommentCountSuccess(int count) { + mTextCommentCount.setText(String.format("评论(%s)", count)); + } + + @Override + public void showGetDetailSuccess(Project project, int strId) { + mPresenter.getCommentCount(project.getId()); + mProject = project; + initProject(project); + mWebView.loadDetailDataAsync(project.getReadme(), new Runnable() { + @Override + public void run() { + + } + }); + } + + @Override + public void showGetDetailFailure(int strId) { + + } + + @Override + public void setPresenter(ProjectDetailContract.Presenter presenter) { + this.mPresenter = presenter; + } + + @SuppressLint({"SimpleDateFormat", "SetTextI18n"}) + private void initProject(Project project) { + if (project.getId() == 0 || project.getOwner() == null) return; + User user = project.getOwner(); + if (user != null) { + mTextName.setText(user.getName() + "/" + project.getName()); + } + mTextLanguage.setText(project.getLanguage()); + mTextStarCount.setText(getCount(project.getStarsCount())); + mTextWatchCount.setText(getCount(project.getWatchesCount())); + mTextForkCount.setText(getCount(project.getForksCount())); + mTextIssuesCount.setText(String.valueOf(project.getIssueCount())); + mTexPrCount.setText(String.valueOf(project.getPullRequestCount())); + mTextDescription.setText(project.getDescription()); + mTextLanguage.setVisibility(TextUtils.isEmpty(project.getLanguage()) ? View.GONE : View.VISIBLE); + if(project.getLastPushTime() != null){ + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + mTextUpdateDate.setText("上次更新于" + StringUtils.formatSomeAgo(dateFormat.format(project.getLastPushTime()))); + } + } + + @Override + public void onResume() { + super.onResume(); + if (mAlertDialog != null) { + mAlertDialog.dismiss(); + } + } + + private String getCount(int count) { + DecimalFormat decimalFormat=new DecimalFormat(".0"); + return count >= 1000 ? String.format("%sk", decimalFormat.format((float)count / 1000)) : String.valueOf(count); + } + + private void toShare() { + String content = mProject.getDescription().trim(); + if (content.length() > 55) { + content = HTMLUtil.delHTMLTag(content); + if (content.length() > 55) + content = StringUtils.getSubString(0, 55, content); + } else { + content = HTMLUtil.delHTMLTag(content); + } + if (TextUtils.isEmpty(content)) + content = ""; + + // 分享 + if (mAlertDialog == null) { + mAlertDialog = new + ShareDialog(getActivity()) + .title(mProject.getOwner().getName() + "/" + mProject.getName()) + .content(content) + .url(mPresenter.getShareUrl()) + .bitmapResID(R.mipmap.ic_git) + .with(); + } + mAlertDialog.show(); + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/detail/ProjectDetailPresenter.java b/app/src/main/java/net/oschina/app/improve/git/detail/ProjectDetailPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..f4ef83c26b85e48cb5e224f346a4fbcfbc4c77f4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/detail/ProjectDetailPresenter.java @@ -0,0 +1,118 @@ +package net.oschina.app.improve.git.detail; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.git.api.API; +import net.oschina.app.improve.git.bean.Project; +import net.oschina.app.ui.empty.EmptyLayout; + +import java.lang.reflect.Type; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2017/3/9. + */ + +class ProjectDetailPresenter implements ProjectDetailContract.Presenter { + private final ProjectDetailContract.View mView; + private final ProjectDetailContract.EmptyView mEmptyView; + private TextHttpResponseHandler mHandler; + private Project mProject; + + ProjectDetailPresenter(ProjectDetailContract.View mView, + ProjectDetailContract.EmptyView mEmptyView, + Project project) { + this.mView = mView; + this.mEmptyView = mEmptyView; + this.mProject = project; + initHandler(); + this.mView.setPresenter(this); + } + + private void initHandler() { + mHandler = new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + mEmptyView.showGetDetailFailure(EmptyLayout.NETWORK_ERROR); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + mProject = bean.getResult(); + mView.showGetDetailSuccess(bean.getResult(), R.string.get_project_detail_success); + mEmptyView.showGetDetailSuccess(EmptyLayout.HIDE_LAYOUT); + } else { + mView.showNetworkError(R.string.state_network_error); + mEmptyView.showGetDetailFailure(EmptyLayout.NETWORK_ERROR); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.state_network_error); + mEmptyView.showGetDetailFailure(EmptyLayout.NETWORK_ERROR); + } + } + }; + } + + @Override + public void getProjectDetail(long id) { + API.getProjectDetail(id, mHandler); + } + + @Override + public void getCommentCount(long id) { + API.getProjectCommentCount(id, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + // TODO: 2017/3/15 + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + mView.showGetCommentCountSuccess(bean.getResult().commentCount); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void getProjectDetail(String name, String pathWithNamespace) { + API.getProjectDetail(pathWithNamespace + "%2F" + name, mHandler); + } + + @Override + public String getShareUrl() { + return String.format("https://gitee.com/%s/", + mProject.getPathWithNamespace()); + } + + private class CommentCount { + private int commentCount; + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/feature/FeatureActivity.java b/app/src/main/java/net/oschina/app/improve/git/feature/FeatureActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..bace7da7a6ee4a8850940eac8127ecdac1689362 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/feature/FeatureActivity.java @@ -0,0 +1,35 @@ +package net.oschina.app.improve.git.feature; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.activities.BaseBackActivity; + +/** + * Created by haibin + * on 2017/3/9. + */ + +public class FeatureActivity extends BackActivity { + + public static void show(Context context) { + context.startActivity(new Intent(context, FeatureActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_feature; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + FeatureFragment fragment = FeatureFragment.newInstance(); + new FeaturePresenter(fragment); + addFragment(R.id.fl_content, fragment); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/feature/FeatureContract.java b/app/src/main/java/net/oschina/app/improve/git/feature/FeatureContract.java new file mode 100644 index 0000000000000000000000000000000000000000..ac17551301869a2750865c4d6ea035037336c2be --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/feature/FeatureContract.java @@ -0,0 +1,20 @@ +package net.oschina.app.improve.git.feature; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.git.bean.Project; + +/** + * Created by haibin + * on 2017/3/9. + */ +interface FeatureContract { + + interface View extends BaseListView { + + } + + interface Presenter extends BaseListPresenter { + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/feature/FeatureFragment.java b/app/src/main/java/net/oschina/app/improve/git/feature/FeatureFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..6d603439e9369e349f30dfaa62e03e02511b945f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/feature/FeatureFragment.java @@ -0,0 +1,29 @@ +package net.oschina.app.improve.git.feature; + +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.git.bean.Project; +import net.oschina.app.improve.git.detail.ProjectDetailActivity; + +/** + * Created by haibin + * on 2017/3/9. + */ + +public class FeatureFragment extends BaseRecyclerFragment + implements FeatureContract.View { + + public static FeatureFragment newInstance() { + return new FeatureFragment(); + } + + @Override + protected void onItemClick(Project project, int position) { + ProjectDetailActivity.show(mContext, project); + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new ProjectAdapter(this); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/feature/FeaturePresenter.java b/app/src/main/java/net/oschina/app/improve/git/feature/FeaturePresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..a77d99feb606afe146379cbe7b851134b449a7fa --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/feature/FeaturePresenter.java @@ -0,0 +1,101 @@ +package net.oschina.app.improve.git.feature; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.git.api.API; +import net.oschina.app.improve.git.bean.Project; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2017/3/9. + */ + +class FeaturePresenter implements FeatureContract.Presenter { + private final FeatureContract.View mView; + private int mPage; + + FeaturePresenter(FeatureContract.View mView) { + this.mView = mView; + mPage = 1; + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + API.getFeatureProjects(1, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + List list = bean.getResult(); + mView.onRefreshSuccess(list); + if (list.size() != 0 && list.size() < 20) { + mView.showNotMore(); + } + mPage = 2; + } else { + mView.showNetworkError(R.string.state_network_error); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + API.getFeatureProjects(mPage, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + List list = bean.getResult(); + mView.onLoadMoreSuccess(list); + if (list.size() != 0 && list.size() < 20) { + mView.showNotMore(); + } + ++mPage; + } else { + mView.showNetworkError(R.string.state_network_error); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/feature/ProjectAdapter.java b/app/src/main/java/net/oschina/app/improve/git/feature/ProjectAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..2848fa37e44d5a9c6df6367c90e0ad642841ea42 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/feature/ProjectAdapter.java @@ -0,0 +1,68 @@ +package net.oschina.app.improve.git.feature; + +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.git.bean.Project; +import net.oschina.app.improve.widget.PortraitView; + +import java.text.DecimalFormat; + +/** + * Created by haibin + * on 2017/3/9. + */ + +class ProjectAdapter extends BaseGeneralRecyclerAdapter { + private DecimalFormat decimalFormat = new DecimalFormat(".0"); + + ProjectAdapter(Callback callback) { + super(callback, ONLY_FOOTER); + setState(STATE_HIDE, false); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ProjectViewHolder(mInflater.inflate(R.layout.item_list_project, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Project item, int position) { + ProjectViewHolder h = (ProjectViewHolder) holder; + h.mImageOwner.setup(0, item.getOwner().getName(), item.getOwner().getNewPortrait()); + h.mImageOwner.setOnClickListener(null); + h.mTextName.setText(item.getOwner().getName() + "/" + item.getName()); + h.mTextDescription.setText(item.getDescription()); + h.mTextLanguage.setText(item.getLanguage()); + h.mTextLanguage.setVisibility(TextUtils.isEmpty(item.getLanguage()) ? View.GONE : View.VISIBLE); + h.mTextViewCount.setText(getCount(item.getWatchesCount())); + h.mTextFavCount.setText(getCount(item.getStarsCount())); + h.mTextForkCount.setText(getCount(item.getForksCount())); + } + + private static class ProjectViewHolder extends RecyclerView.ViewHolder { + PortraitView mImageOwner; + TextView mTextName, mTextDescription, mTextViewCount, + mTextFavCount, mTextForkCount, mTextLanguage; + + ProjectViewHolder(View itemView) { + super(itemView); + mImageOwner = (PortraitView) itemView.findViewById(R.id.civ_owner); + mTextName = (TextView) itemView.findViewById(R.id.tv_name); + mTextDescription = (TextView) itemView.findViewById(R.id.tv_description); + mTextViewCount = (TextView) itemView.findViewById(R.id.tv_view_count); + mTextFavCount = (TextView) itemView.findViewById(R.id.tv_fav_count); + mTextForkCount = (TextView) itemView.findViewById(R.id.tv_fork_count); + mTextLanguage = (TextView) itemView.findViewById(R.id.tv_language); + } + } + + private String getCount(int count) { + return count >= 1000 ? String.format("%sk", decimalFormat.format((float) count / 1000)) : String.valueOf(count); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/GistActivity.java b/app/src/main/java/net/oschina/app/improve/git/gist/GistActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..dfb51658475af0727672fbbbc5d6cd09fc03d9df --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/GistActivity.java @@ -0,0 +1,33 @@ +package net.oschina.app.improve.git.gist; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; + +/** + * 代码片段 + * Created by haibin on 2017/5/10. + */ + +public class GistActivity extends BackActivity { + public static void show(Context context) { + context.startActivity(new Intent(context, GistActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_gist; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + GistFragment fragment = GistFragment.newInstance(); + new GistPresenter(fragment); + addFragment(R.id.fl_content, fragment); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/GistAdapter.java b/app/src/main/java/net/oschina/app/improve/git/gist/GistAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..28a99de1f97f2e8a897a68b98c6b2d86cd965bab --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/GistAdapter.java @@ -0,0 +1,70 @@ +package net.oschina.app.improve.git.gist; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.git.bean.Gist; +import net.oschina.app.improve.widget.PortraitView; + +import java.text.DecimalFormat; + +/** + * 代码片段适配器 + * Created by haibin on 2017/5/10. + */ + +class GistAdapter extends BaseRecyclerAdapter { + private DecimalFormat decimalFormat = new DecimalFormat(".0"); + + GistAdapter(Context context) { + super(context, ONLY_FOOTER); + setState(STATE_HIDE, false); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new GistViewHolder(mInflater.inflate(R.layout.item_list_gist, parent, false)); + } + + @SuppressLint("SetTextI18n") + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Gist item, int position) { + GistViewHolder h = (GistViewHolder) holder; + h.mImageOwner.setup(0, item.getOwner().getName(), item.getOwner().getNewPortrait()); + h.mImageOwner.setOnClickListener(null); + h.mTextSummary.setText(item.getSummary()); + h.mTextCategory.setText(item.getCategory()); + h.mTextLanguage.setText(item.getLanguage()); + h.mTextLanguage.setVisibility(TextUtils.isEmpty(item.getLanguage()) ? View.GONE : View.VISIBLE); + h.mTextCategory.setVisibility(TextUtils.isEmpty(item.getCategory()) ? View.GONE : View.VISIBLE); + h.mTextFavCount.setText(getCount(item.getStartCounts())); + h.mTextForkCount.setText(getCount(item.getForkCounts())); + } + + private static class GistViewHolder extends RecyclerView.ViewHolder { + PortraitView mImageOwner; + TextView mTextSummary, + mTextFavCount, mTextForkCount, mTextLanguage,mTextCategory; + + GistViewHolder(View itemView) { + super(itemView); + mImageOwner = (PortraitView) itemView.findViewById(R.id.civ_owner); + mTextSummary = (TextView) itemView.findViewById(R.id.tv_summary); + mTextFavCount = (TextView) itemView.findViewById(R.id.tv_fav_count); + mTextForkCount = (TextView) itemView.findViewById(R.id.tv_fork_count); + mTextLanguage = (TextView) itemView.findViewById(R.id.tv_language); + mTextCategory = (TextView) itemView.findViewById(R.id.tv_category); + } + } + + private String getCount(int count) { + return count >= 1000 ? String.format("%sk", decimalFormat.format((float) count / 1000)) : String.valueOf(count); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/GistContract.java b/app/src/main/java/net/oschina/app/improve/git/gist/GistContract.java new file mode 100644 index 0000000000000000000000000000000000000000..bb39e54abbe338b21862094f8266a901d9f99dd2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/GistContract.java @@ -0,0 +1,19 @@ +package net.oschina.app.improve.git.gist; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.git.bean.Gist; + +/** + * 代码片段 + * Created by haibin on 2017/5/10. + */ +interface GistContract { + interface View extends BaseListView { + + } + + interface Presenter extends BaseListPresenter { + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/GistFragment.java b/app/src/main/java/net/oschina/app/improve/git/gist/GistFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..bd318a28f0b6dd80e7a3ef8d85540eb58cccb63f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/GistFragment.java @@ -0,0 +1,29 @@ +package net.oschina.app.improve.git.gist; + +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.git.bean.Gist; +import net.oschina.app.improve.git.gist.detail.GistDetailActivity; + +/** + * 代码片段 + * Created by haibin on 2017/5/10. + */ + +public class GistFragment extends BaseRecyclerFragment implements + GistContract.View { + + static GistFragment newInstance() { + return new GistFragment(); + } + + @Override + protected void onItemClick(Gist gist, int position) { + GistDetailActivity.show(mContext, gist); + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new GistAdapter(mContext); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/GistPresenter.java b/app/src/main/java/net/oschina/app/improve/git/gist/GistPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..3744b4da55af8886932c40e66af5bc3f25a18b49 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/GistPresenter.java @@ -0,0 +1,101 @@ +package net.oschina.app.improve.git.gist; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.git.api.API; +import net.oschina.app.improve.git.bean.Gist; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * 代码片段 + * Created by haibin on 2017/5/10. + */ + +class GistPresenter implements GistContract.Presenter { + private final GistContract.View mView; + private int mPage; + + GistPresenter(GistContract.View mView) { + this.mView = mView; + mPage = 1; + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + API.getGists("", "", 1, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + List list = bean.getResult(); + mView.onRefreshSuccess(list); + if (list.size() != 0 && list.size() < 20) { + mView.showNotMore(); + } + mPage = 2; + } else { + mView.showNetworkError(R.string.state_network_error); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + API.getGists("", "", mPage, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + List list = bean.getResult(); + mView.onLoadMoreSuccess(list); + if (list.size() != 0 && list.size() < 20) { + mView.showNotMore(); + } + ++mPage; + } else { + mView.showNetworkError(R.string.state_network_error); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/comment/GistCommentActivity.java b/app/src/main/java/net/oschina/app/improve/git/gist/comment/GistCommentActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..c82d401ed3a3fce2ecb6008a87329f373674db02 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/comment/GistCommentActivity.java @@ -0,0 +1,118 @@ +package net.oschina.app.improve.git.gist.comment; + +import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.View; +import android.widget.LinearLayout; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.git.bean.Comment; +import net.oschina.app.improve.git.bean.Gist; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; + +/** + * 代码片段评论 + * Created by haibin on 2017/5/11. + */ + +public class GistCommentActivity extends BackActivity implements GistCommentContract.Action, View.OnClickListener { + private GistCommentPresenter mPresenter; + protected CommentBar mDelegation; + private String mMentionStr = ""; + protected boolean mInputDoubleEmpty = false; + + public static void show(Context context, Gist gist) { + Intent intent = new Intent(context, GistCommentActivity.class); + intent.putExtra("gist", gist); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_gist_comment; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + final Gist gist = (Gist) getIntent().getSerializableExtra("gist"); + GistCommentFragment fragment = GistCommentFragment.newInstance(); + addFragment(R.id.fl_content, fragment); + mPresenter = new GistCommentPresenter(fragment, this, gist); + LinearLayout layComment = (LinearLayout) findViewById(R.id.ll_comment); + mDelegation = CommentBar.delegation(this, layComment); + mDelegation.hideFav(); + mDelegation.hideCommentCount(); + mDelegation.getBottomSheet().hideSyncAction(); + mDelegation.getBottomSheet().hideMentionAction(); + mDelegation.getBottomSheet().setMentionListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if ((AccountHelper.isLogin())) { + UserSelectFriendsActivity.show(GistCommentActivity.this, mDelegation.getBottomSheet().getEditText()); + } else { + LoginActivity.show(GistCommentActivity.this, 1); + } + } + }); + mDelegation.getBottomSheet().getEditText().setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL) { + handleKeyDel(); + } + return false; + } + }); + mDelegation.getBottomSheet().setCommitListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mPresenter.addComment(mMentionStr + mDelegation.getBottomSheet().getCommentText()); + } + }); + } + + @Override + public void onClick(View v) { + Comment comment = (Comment) v.getTag(); + mMentionStr = "回复 @" + comment.getAuthor().getName() + ":"; + mDelegation.getBottomSheet().show(mMentionStr); + } + + @Override + public void showAddCommentSuccess(Comment comment, int strId) { + mDelegation.getBottomSheet().getEditText().setText(""); + mDelegation.getBottomSheet().getEditText().setHint("发表评论"); + mMentionStr = ""; + mDelegation.getBottomSheet().dismiss(); + } + + @Override + public void showAddCommentFailure(int strId) { + + } + + private void handleKeyDel() { + if (!TextUtils.isEmpty(mMentionStr)) { + if (TextUtils.isEmpty(mDelegation.getBottomSheet().getCommentText())) { + if (mInputDoubleEmpty) { + mMentionStr = ""; + mDelegation.setCommentHint("发表评论"); + mDelegation.getBottomSheet().getEditText().setHint("发表评论"); + } else { + mInputDoubleEmpty = true; + } + } else { + mInputDoubleEmpty = false; + } + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/comment/GistCommentContract.java b/app/src/main/java/net/oschina/app/improve/git/gist/comment/GistCommentContract.java new file mode 100644 index 0000000000000000000000000000000000000000..7914497882dadaa55ec76ac0ca7b424f2b58b87b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/comment/GistCommentContract.java @@ -0,0 +1,28 @@ +package net.oschina.app.improve.git.gist.comment; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.git.bean.Comment; + +/** + * 代码片段评论 + * Created by haibin on 2017/5/11. + */ + +interface GistCommentContract { + interface Action { + void showAddCommentSuccess(Comment comment, int strId); + + void showAddCommentFailure(int strId); + } + + interface View extends BaseListView { + void showAddCommentSuccess(Comment comment, int strId); + + void showAddCommentFailure(int strId); + } + + interface Presenter extends BaseListPresenter { + void addComment(String content); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/comment/GistCommentFragment.java b/app/src/main/java/net/oschina/app/improve/git/gist/comment/GistCommentFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..ffb5d6c4a1f013bf8df98d63b2f7065a2ea44146 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/comment/GistCommentFragment.java @@ -0,0 +1,46 @@ +package net.oschina.app.improve.git.gist.comment; + +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.git.bean.Comment; +import net.oschina.app.improve.git.comment.CommentAdapter; +import net.oschina.app.improve.widget.SimplexToast; + +/** + * 代码片段评论 + * Created by haibin on 2017/5/11. + */ + +public class GistCommentFragment extends BaseRecyclerFragment + implements GistCommentContract.View { + + static GistCommentFragment newInstance() { + return new GistCommentFragment(); + } + + @Override + protected void onItemClick(Comment comment, int position) { + // TODO: 2017/5/11 + } + + @Override + public void showAddCommentSuccess(Comment comment, int strId) { + mAdapter.addItem(comment); + } + + @Override + public void showNotMore() { + super.showNotMore(); + mRefreshLayout.setCanLoadMore(false); + } + + @Override + public void showAddCommentFailure(int strId) { + SimplexToast.show(mContext, strId); + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new CommentAdapter(this); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/comment/GistCommentPresenter.java b/app/src/main/java/net/oschina/app/improve/git/gist/comment/GistCommentPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..4398ecb8eb7f0800a9295fa82aed5353897c4067 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/comment/GistCommentPresenter.java @@ -0,0 +1,140 @@ +package net.oschina.app.improve.git.gist.comment; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.git.api.API; +import net.oschina.app.improve.git.bean.Comment; +import net.oschina.app.improve.git.bean.Gist; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * 代码片段评论 + * Created by haibin on 2017/5/11. + */ + +class GistCommentPresenter implements GistCommentContract.Presenter { + private final GistCommentContract.View mView; + private final GistCommentContract.Action mAction; + private final Gist mGist; + private String mToken; + + GistCommentPresenter(GistCommentContract.View mView, GistCommentContract.Action mAction, Gist gist) { + this.mView = mView; + this.mGist = gist; + this.mAction = mAction; + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + API.getGistComments(mGist.getId(), null, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + mToken = bean.getResult().getNextPageToken(); + List list = bean.getResult().getItems(); + if (list != null) { + mView.onRefreshSuccess(list); + if (list.size() < 20) { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + API.getGistComments(mGist.getId(), mToken, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + mToken = bean.getResult().getNextPageToken(); + List list = bean.getResult().getItems(); + if (list != null && list.size() != 0) { + mView.onLoadMoreSuccess(list); + } else { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void addComment(String content) { + API.pubGistComment(mGist.getId(), mGist.getSummary(), mGist.getUrl(), content, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showAddCommentFailure(R.string.pub_comment_failed); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + mAction.showAddCommentSuccess(bean.getResult(), R.string.pub_comment_success); + mView.showAddCommentSuccess(bean.getResult(), R.string.pub_comment_success); + } else { + mAction.showAddCommentFailure(R.string.pub_comment_failed); + mView.showAddCommentFailure(R.string.pub_comment_failed); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showAddCommentFailure(R.string.pub_comment_failed); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistDetailActivity.java b/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistDetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..0833b8550afefc6d2ba2263545230243bb609263 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistDetailActivity.java @@ -0,0 +1,85 @@ +package net.oschina.app.improve.git.gist.detail; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.support.v7.app.ActionBar; +import android.view.View; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.git.bean.Gist; +import net.oschina.app.ui.empty.EmptyLayout; + +import butterknife.Bind; + +/** + * 代码片段详情 + * Created by haibin on 2017/5/10. + */ + +public class GistDetailActivity extends BackActivity implements GistDetailContract.EmptyView { + private GistDetailPresenter mPresenter; + @Bind(R.id.emptyLayout) + EmptyLayout mEmptyLayout; + + public static void show(Context context, Gist gist) { + Intent intent = new Intent(context, GistDetailActivity.class); + intent.putExtra("gist", gist); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_gist_detail; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + final Gist gist = (Gist) + getIntent().getSerializableExtra("gist"); + GistDetailFragment fragment = GistDetailFragment.newInstance(gist); + mPresenter = new GistDetailPresenter(fragment, this); + addFragment(R.id.fl_content, fragment); + mPresenter.getGistDetail(gist.getId()); + mPresenter.getCommentCount(gist.getId()); + mEmptyLayout.setOnLayoutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mEmptyLayout.getErrorState() != EmptyLayout.NETWORK_LOADING) { + mEmptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + mPresenter.getGistDetail(gist.getId()); + } + } + }); + } + + @Override + public void showGetDetailSuccess(int strId) { + mEmptyLayout.setErrorType(strId); + } + + @Override + public void showGetDetailFailure(int strId) { + mEmptyLayout.setErrorType(strId); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + ActionBar bar = getSupportActionBar(); + if (bar != null) + bar.hide(); + mPresenter.changeConfig(true); + } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + mPresenter.changeConfig(false); + ActionBar bar = getSupportActionBar(); + if (bar != null) + bar.show(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistDetailContract.java b/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistDetailContract.java new file mode 100644 index 0000000000000000000000000000000000000000..e378f55d96abae885ec31d9e6f7bc0eb1d8b7b0b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistDetailContract.java @@ -0,0 +1,48 @@ +package net.oschina.app.improve.git.gist.detail; + +import net.oschina.app.improve.base.BasePresenter; +import net.oschina.app.improve.base.BaseView; +import net.oschina.app.improve.git.bean.Gist; + +/** + * 代码片段详情 + * Created by haibin on 2017/5/10. + */ + +interface GistDetailContract { + + interface EmptyView { + void showGetDetailSuccess(int strId); + + void showGetDetailFailure(int strId); + } + + interface View extends BaseView { + void showGetDetailSuccess(Gist gist, int strId); + + void showGetCommentCountSuccess(int count); + + /** + * 显示横屏 + */ + void showLandscape(); + + /** + * 显示竖屏 + */ + void showPortrait(); + } + + interface Presenter extends BasePresenter { + void getGistDetail(String id); + + void getCommentCount(String id); + + /** + * 改变配置 + * + * @param isLandscape 是否是横屏 + */ + void changeConfig(boolean isLandscape); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistDetailFragment.java b/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistDetailFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..45e220a9ff6d472d0aca982a97d9f7792b87b072 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistDetailFragment.java @@ -0,0 +1,286 @@ +package net.oschina.app.improve.git.gist.detail; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.graphics.Color; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.dialog.ShareDialog; +import net.oschina.app.improve.git.bean.CodeDetail; +import net.oschina.app.improve.git.bean.Gist; +import net.oschina.app.improve.git.gist.comment.GistCommentActivity; +import net.oschina.app.improve.git.utils.MarkdownUtils; +import net.oschina.app.improve.git.utils.SourceEditor; +import net.oschina.app.improve.media.ImageGalleryActivity; +import net.oschina.app.improve.media.Util; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.StringUtils; + +import java.text.SimpleDateFormat; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 代码片段详情 + * Created by haibin on 2017/5/10. + */ + +public class GistDetailFragment extends BaseFragment implements GistDetailContract.View, View.OnClickListener { + private SourceEditor mEditor; + @Bind(R.id.tv_summary) + TextView mTextSummary; + @Bind(R.id.tv_start_count) + TextView mTexStartCount; + @Bind(R.id.tv_fork_count) + TextView mTextForkCount; + + @Bind(R.id.tv_language) + TextView mTextLanguage; + @Bind(R.id.tv_category) + TextView mTextCategory; + @Bind(R.id.tv_last_update) + TextView mTextLastUpdate; + @Bind(R.id.tv_comment_count) + TextView mTextCommentCount; + + @Bind(R.id.ll_content) + LinearLayout mLinearContent; + @Bind(R.id.line1) + View mLine1; + @Bind(R.id.line2) + View mLine2; + @Bind(R.id.ll_tool) + LinearLayout mLinearTool; + private Gist mGist; + private ShareDialog mAlertDialog; + + static GistDetailFragment newInstance(Gist gist) { + GistDetailFragment fragment = new GistDetailFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable("gist", gist); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_gist_detail; + } + + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + mGist = (Gist) bundle.getSerializable("gist"); + } + + @SuppressWarnings("MalformedFormatString") + @SuppressLint("DefaultLocale") + private void init(Gist gist) { + assert gist != null; + mTextSummary.setText(gist.getSummary()); + mTexStartCount.setText(String.valueOf(gist.getStartCounts())); + mTextForkCount.setText(String.valueOf(gist.getForkCounts())); + mTextCategory.setText(gist.getCategory()); + mTextLanguage.setText(gist.getLanguage()); + if (gist.getLastUpdateDate() != null) { + @SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + mTextLastUpdate.setText(String.format("最后更新于%s", StringUtils.formatSomeAgo(dateFormat.format(gist.getLastUpdateDate())))); + } + //mTextDescription.setVisibility(TextUtils.isEmpty(gist.getDescription()) ? View.GONE : View.VISIBLE); + mTextLanguage.setVisibility(TextUtils.isEmpty(gist.getLanguage()) ? View.GONE : View.VISIBLE); + mTextCategory.setVisibility(TextUtils.isEmpty(gist.getCategory()) ? View.GONE : View.VISIBLE); + } + + @SuppressLint({"SetJavaScriptEnabled"}) + @SuppressWarnings("all") + private void addFiles(Gist.File[] files) { + mLinearContent.removeAllViews(); + for (Gist.File file : files) {//不确定排序 + if (file.getType() == Gist.File.FILE_CODE) { + TextView textName = new TextView(mContext); + textName.setText(file.getName()); + textName.setTextColor(Color.parseColor("#111111")); + textName.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMargins(Util.dipTopx(mContext, 16), Util.dipTopx(mContext, 8), Util.dipTopx(mContext, 16), Util.dipTopx(mContext, 8)); + textName.setLayoutParams(params); + mLinearContent.addView(textName); + + View view = new View(mContext); + LinearLayout.LayoutParams paramsLine = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2); + view.setBackgroundColor(mContext.getResources().getColor(R.color.list_divider_color)); + view.setLayoutParams(paramsLine); + mLinearContent.addView(view); + + final GistWebView webView = new GistWebView(mContext); + WebSettings settings = webView.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setDefaultFontSize(10); + settings.setAllowContentAccess(true); + webView.setWebChromeClient(new WebChromeClient() { + }); + mEditor = new SourceEditor(webView); + mLinearContent.addView(webView); + mEditor.setMarkdown(MarkdownUtils.isMarkdown(file.getName())); + CodeDetail detail = new CodeDetail(); + detail.setContent(file.getContent()); + mEditor.setSource(file.getName(), detail); + } + } + for (final Gist.File file : files) { + if (file.getType() == Gist.File.FILE_BIN) { + TextView textName = new TextView(mContext); + textName.setText(file.getName()); + textName.setTextColor(Color.parseColor("#24cf5f")); + textName.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMargins(Util.dipTopx(mContext, 16), Util.dipTopx(mContext, 8), Util.dipTopx(mContext, 16), Util.dipTopx(mContext, 8)); + textName.setLayoutParams(params); + mLinearContent.addView(textName); + textName.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(); + intent.setAction("android.intent.action.VIEW"); + Uri content_url = Uri.parse(file.getContent()); + intent.setData(content_url); + startActivity(intent); + } + }); + } + } + + for (final Gist.File file : files) { + if (file.getType() == Gist.File.FILE_IMAGE) { + TextView textName = new TextView(mContext); + textName.setText(file.getName()); + textName.setTextColor(Color.parseColor("#111111")); + textName.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMargins(Util.dipTopx(mContext, 16), Util.dipTopx(mContext, 8), Util.dipTopx(mContext, 16), Util.dipTopx(mContext, 8)); + textName.setLayoutParams(params); + mLinearContent.addView(textName); + + ImageView imageView = new ImageView(mContext); + params.setMargins(Util.dipTopx(mContext, 16), Util.dipTopx(mContext, 8), Util.dipTopx(mContext, 16), Util.dipTopx(mContext, 8)); + imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + imageView.setLayoutParams(params); + getImgLoader().load(file.getContent()) + .fitCenter() + .into(imageView); + mLinearContent.addView(imageView); + imageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ImageGalleryActivity.show(mContext,file.getContent()); + } + }); + } + } + } + + @SuppressLint("SetJavaScriptEnabled") + @Override + protected void initWidget(View root) { + super.initWidget(root); + init(mGist); + } + + @OnClick({R.id.ll_comment, R.id.ll_share}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.ll_comment: + GistCommentActivity.show(mContext, mGist); + break; + case R.id.ll_share: + toShare(); + break; + } + } + + @Override + public void setPresenter(GistDetailContract.Presenter presenter) { + + } + + @Override + public void showNetworkError(int strId) { + + } + + @Override + public void showGetDetailSuccess(Gist gist, int strId) { + mGist = gist; + init(gist); + if (gist.getFiles() != null && gist.getFiles().length != 0) { + addFiles(gist.getFiles()); + } + } + + @Override + public void onResume() { + super.onResume(); + if (mAlertDialog != null) + mAlertDialog.dismiss(); + } + + @Override + public void showGetCommentCountSuccess(int count) { + mTextCommentCount.setText(String.format("评论(%s)", count)); + } + + @Override + public void showLandscape() { + mLinearTool.setVisibility(View.GONE); + mLine1.setVisibility(View.GONE); + mLine2.setVisibility(View.GONE); + } + + @Override + public void showPortrait() { + mLinearTool.setVisibility(View.VISIBLE); + mLine1.setVisibility(View.VISIBLE); + mLine2.setVisibility(View.VISIBLE); + } + + private void toShare() { + String content = mGist.getSummary().trim(); + if (content.length() > 55) { + content = HTMLUtil.delHTMLTag(content); + if (content.length() > 55) + content = StringUtils.getSubString(0, 55, content); + } else { + content = HTMLUtil.delHTMLTag(content); + } + if (TextUtils.isEmpty(content)) + content = ""; + + // 分享 + if (mAlertDialog == null) { + mAlertDialog = new + ShareDialog(getActivity()) + .title(mGist.getOwner().getName() + "/" + mGist.getSummary()) + .content(content) + .url(mGist.getUrl()) + .bitmapResID(R.mipmap.ic_git) + .with(); + } + mAlertDialog.show(); + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistDetailPresenter.java b/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistDetailPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..6d7e7540cfe532a14bd696b28267f4bba5d7e20c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistDetailPresenter.java @@ -0,0 +1,104 @@ +package net.oschina.app.improve.git.gist.detail; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.git.api.API; +import net.oschina.app.improve.git.bean.Gist; +import net.oschina.app.ui.empty.EmptyLayout; + +import java.lang.reflect.Type; + +import cz.msebera.android.httpclient.Header; + +/** + * 代码片段详情 + * Created by haibin on 2017/5/10. + */ + +class GistDetailPresenter implements GistDetailContract.Presenter { + private final GistDetailContract.View mView; + private final GistDetailContract.EmptyView mEmptyView; + + GistDetailPresenter(GistDetailContract.View mView, GistDetailContract.EmptyView mEmptyView) { + this.mView = mView; + this.mEmptyView = mEmptyView; + this.mView.setPresenter(this); + } + + @Override + public void getGistDetail(String id) { + API.getGistDetail(id, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + mEmptyView.showGetDetailFailure(EmptyLayout.NETWORK_ERROR); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken() { + }.getType(); + Gist bean = new Gson().fromJson(responseString, type); + if (bean != null) { + mView.showGetDetailSuccess(bean, R.string.get_project_detail_success); + mEmptyView.showGetDetailSuccess(EmptyLayout.HIDE_LAYOUT); + } else { + mView.showNetworkError(R.string.state_network_error); + mEmptyView.showGetDetailFailure(EmptyLayout.NETWORK_ERROR); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.state_network_error); + mEmptyView.showGetDetailFailure(EmptyLayout.NETWORK_ERROR); + } + } + }); + } + + @Override + public void getCommentCount(String id) { + API.getGistCommentCount(id, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + // TODO: 2017/5/11 + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + mView.showGetCommentCountSuccess(bean.getResult().commentCount); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void changeConfig(boolean isLandscape) { + if (isLandscape) + mView.showLandscape(); + else + mView.showPortrait(); + } + + private class CommentCount { + private int commentCount; + + public int getCommentCount() { + return commentCount; + } + + public void setCommentCount(int commentCount) { + this.commentCount = commentCount; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistWebView.java b/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistWebView.java new file mode 100644 index 0000000000000000000000000000000000000000..96ff1680a2ca9142e3e5fc47095aeb64ceed0bbc --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/gist/detail/GistWebView.java @@ -0,0 +1,49 @@ +package net.oschina.app.improve.git.gist.detail; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.MotionEvent; +import android.webkit.WebView; + +/** + * 代码片段详情滚动 + * Created by huanghaibin on 2017/7/17. + */ + +public class GistWebView extends WebView { + private float mDownX; + private boolean isRequsest; + + public GistWebView(Context context) { + super(context); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + int action = event.getAction(); + int count = event.getPointerCount(); + if(count >= 2 && !isRequsest){ + requestDisallowInterceptTouchEvent(true); + isRequsest = true; + return super.onTouchEvent(event); + } + switch (action) { + case MotionEvent.ACTION_DOWN: + mDownX = event.getX(); + isRequsest = false; + break; + case MotionEvent.ACTION_MOVE: + float dx = event.getX() - mDownX; + if (Math.abs(dx) >= 20 && !isRequsest) { + requestDisallowInterceptTouchEvent(true); + isRequsest = true; + } + break; + case MotionEvent.ACTION_UP: + isRequsest = false; + break; + } + return super.onTouchEvent(event); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/tree/TreeActivity.java b/app/src/main/java/net/oschina/app/improve/git/tree/TreeActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..34cc932d52edc2aa2b39dbb452be56e85a8610b0 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/tree/TreeActivity.java @@ -0,0 +1,101 @@ +package net.oschina.app.improve.git.tree; + +import android.content.Context; +import android.content.Intent; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.git.bean.Branch; +import net.oschina.app.improve.git.bean.Project; +import net.oschina.app.improve.git.branch.BranchPopupWindow; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * Created by haibin + * on 2017/3/13. + */ + +public class TreeActivity extends BackActivity implements View.OnClickListener, BranchPopupWindow.Callback { + private BranchPopupWindow mBranchPopupWindow; + + @Bind(R.id.ll_branch) + LinearLayout mLinearBranch; + @Bind(R.id.tv_branch) + TextView mTextBranch; + @Bind(R.id.iv_arrow) + ImageView mImageArrow; + + private TreeContract.Presenter mPresenter; + + public static void show(Context context, Project project) { + Intent intent = new Intent(context, TreeActivity.class); + intent.putExtra("project", project); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_tree; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + TreeFragment fragment = TreeFragment.newInstance(); + addFragment(R.id.fl_content, fragment); + Project project = (Project) getIntent().getSerializableExtra("project"); + mTextBranch.setText(project.getDefaultBranch()); + mBranchPopupWindow = new BranchPopupWindow(this, project.getId(), this); + mPresenter = new TreePresenter(fragment, project); + } + + @Override + protected void initData() { + super.initData(); + } + + @OnClick({R.id.ll_branch}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.ll_branch: + mBranchPopupWindow.showAsDropDown(mLinearBranch); + break; + } + } + + @Override + public void onSelect(Branch branch) { + mTextBranch.setText(branch.getName()); + mPresenter.setBranch(branch.getName()); + mPresenter.onRefreshing(); + } + + @Override + public void onDismiss() { + mImageArrow.setImageResource(R.mipmap.ic_arrow_bottom); + } + + @Override + public void onShow() { + mImageArrow.setImageResource(R.mipmap.ic_arrow_top); + } + + @Override + public void onBackPressed() { + TreePresenter presenter = (TreePresenter) mPresenter; + if (presenter.isCanBack()) { + super.onBackPressed(); + } else { + presenter.preLoad(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/tree/TreeAdapter.java b/app/src/main/java/net/oschina/app/improve/git/tree/TreeAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..fe5c7d1fc1b2a3c60cdbb3a9d8a9bd3451d9cae6 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/tree/TreeAdapter.java @@ -0,0 +1,46 @@ +package net.oschina.app.improve.git.tree; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.git.bean.Tree; + +/** + * Created by haibin + * on 2017/3/13. + */ + +class TreeAdapter extends BaseRecyclerAdapter { + TreeAdapter(Context context) { + super(context, NEITHER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new TreeViewHolder(mInflater.inflate(R.layout.item_list_tree, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Tree item, int position) { + TreeViewHolder h = (TreeViewHolder) holder; + h.mImageType.setImageResource(item.isFile() ? R.mipmap.ic_file : R.mipmap.ic_folder); + h.mTextName.setText(item.getName()); + } + + private static class TreeViewHolder extends RecyclerView.ViewHolder { + ImageView mImageType; + TextView mTextName; + + TreeViewHolder(View itemView) { + super(itemView); + mImageType = (ImageView) itemView.findViewById(R.id.iv_tree_type); + mTextName = (TextView) itemView.findViewById(R.id.tv_name); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/tree/TreeContract.java b/app/src/main/java/net/oschina/app/improve/git/tree/TreeContract.java new file mode 100644 index 0000000000000000000000000000000000000000..93f39a2687f7a754236bc6bd215e605ff3e434a1 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/tree/TreeContract.java @@ -0,0 +1,35 @@ +package net.oschina.app.improve.git.tree; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.git.bean.Project; +import net.oschina.app.improve.git.bean.Tree; + +/** + * 代码仓库 + * Created by haibin + * on 2017/3/13. + */ + +interface TreeContract { + interface View extends BaseListView { + } + + interface Presenter extends BaseListPresenter { + Project getProject(); + + void setBranch(String branch); + + String getBranch(); + + String getPath(); + + void preLoad(int position); + + void preLoad(); + + void nextLoad(String path); + + String getImageUrl(String fileName); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/tree/TreeFragment.java b/app/src/main/java/net/oschina/app/improve/git/tree/TreeFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..4e28e77559fabbee8897ba7477b31b61cbeccfad --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/tree/TreeFragment.java @@ -0,0 +1,53 @@ +package net.oschina.app.improve.git.tree; + +import android.view.View; + +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.git.bean.Tree; +import net.oschina.app.improve.git.code.CodeDetailActivity; +import net.oschina.app.improve.git.utils.CodeFileUtil; +import net.oschina.app.improve.media.ImageGalleryActivity; +import net.oschina.app.improve.utils.DialogHelper; + +/** + * Created by haibin + * on 2017/3/13. + */ + +public class TreeFragment extends BaseRecyclerFragment implements + TreeContract.View { + + static TreeFragment newInstance() { + return new TreeFragment(); + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mRefreshLayout.setEnabled(false); + } + + @Override + protected void onItemClick(Tree tree, int position) { + if (tree.isFile()) { + if (CodeFileUtil.isCodeTextFile(tree.getName())) { + CodeDetailActivity.show(mContext, + mPresenter.getProject(), + mPresenter.getPath() + tree.getName(), + mPresenter.getBranch()); + } else if (CodeFileUtil.isImage(tree.getName())) { + ImageGalleryActivity.show(mContext, mPresenter.getImageUrl(tree.getName())); + } else { + DialogHelper.getMessageDialog(mContext, "温馨提醒", "该文件不支持在线预览", "取消").show(); + } + } else { + mPresenter.nextLoad(tree.getName()); + } + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new TreeAdapter(mContext); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/tree/TreePresenter.java b/app/src/main/java/net/oschina/app/improve/git/tree/TreePresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..b6cb6c72ee3ca5fd6d71f562fb6c3e325d6a8b5f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/tree/TreePresenter.java @@ -0,0 +1,197 @@ +package net.oschina.app.improve.git.tree; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.improve.git.api.API; +import net.oschina.app.improve.git.bean.Project; +import net.oschina.app.improve.git.bean.Tree; + +import java.lang.reflect.Type; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2017/3/13. + */ + +class TreePresenter implements TreeContract.Presenter { + private final TreeContract.View mView; + private Project mProject; + private String mBranch; + private List mPaths; + private HashMap> mTreeMap = new HashMap<>(); + private LinkedHashMap> mCodeMap; + private boolean isLoading; + + TreePresenter(TreeContract.View mView, Project project) { + this.mView = mView; + this.mProject = project; + mBranch = project.getDefaultBranch(); + mPaths = new ArrayList<>(); + mCodeMap = new LinkedHashMap<>(); + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + if (mTreeMap.containsKey(mBranch)) { + mView.onRefreshSuccess(mTreeMap.get(mBranch)); + return; + } + if (isLoading) return; + isLoading = true; + API.getCodeTree(mProject.getId(), getPath(), mBranch, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + List bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.size() != 0) { + mTreeMap.put(mBranch, bean); + mCodeMap.put(0, bean); + mView.onRefreshSuccess(bean); + isLoading = false; + } else { + mView.showNetworkError(R.string.state_network_error); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + // TODO: 2017/3/13 + } + + private void remove(int position) { + if (mCodeMap.size() < position && mCodeMap.containsKey(position)) + return; + int size = mCodeMap.size(); + for (int i = position; i <= size; i++) { + mCodeMap.remove(i + 1); + if (mPaths.size() > position && position >= 0) + mPaths.remove(position);//一直移除最后一项 + } + isLoading = false; + } + + @Override + public void preLoad(int position) { + if (mPaths.size() < position) + return; + isLoading = true; + List codes = mCodeMap.get(position); + mView.onRefreshSuccess(codes); + remove(position); + } + + @Override + public void preLoad() { + preLoad(mPaths.size() - 1); + } + + @Override + public void nextLoad(final String path) { + if (isLoading) return; + isLoading = true; + API.getCodeTree(mProject.getId(), + getPath() + path + "/", + mBranch, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + List bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.size() != 0) { + mCodeMap.put(mPaths.size() + 1, bean); + mPaths.add(path); + mView.onRefreshSuccess(bean); + + } else { + mView.showNetworkError(R.string.state_network_error); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + } + + @Override + public void onFinish() { + super.onFinish(); + isLoading = false; + } + }); + } + + @Override + public String getPath() { + StringBuilder sb = new StringBuilder(); + for (String s : mPaths) { + sb.append(s).append("/"); + } + return sb.toString(); + } + + boolean isCanBack() { + return mPaths.size() == 0; + } + + @Override + public Project getProject() { + return mProject; + } + + @SuppressWarnings("deprecation") + @Override + public String getImageUrl(String fileName) { + return "https://gitee.com/" + mProject.getPathWithNamespace() + "/" + "raw" + "/" + mBranch + "/" + URLEncoder.encode + (getPath() + fileName); + } + + /** + * 切换分支清空缓存代码仓库 + */ + @Override + public void setBranch(String branch) { + this.mBranch = branch; + mPaths.clear(); + mTreeMap.clear(); + mCodeMap.clear(); + } + + @Override + public String getBranch() { + return mBranch; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/utils/Base64Util.java b/app/src/main/java/net/oschina/app/improve/git/utils/Base64Util.java new file mode 100644 index 0000000000000000000000000000000000000000..8948dadc65d63d2c98aecd9c32bc8739ba0b175c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/utils/Base64Util.java @@ -0,0 +1,306 @@ +package net.oschina.app.improve.git.utils; + + +import java.io.UnsupportedEncodingException; +import java.text.MessageFormat; +import java.util.Arrays; + +/** + * Encodes and decodes to and from Base64 notation. + *

    + * I am placing this code in the Public Domain. Do with it as you will. This + * software comes with no guarantees or warranties but with plenty of + * well-wishing instead! Please visit http://iharder.net/base64 periodically + * to check for updates or to contribute improvements. + *

    + * + * @author Robert Harder + * @author rob@iharder.net + * @version 2.1, stripped to minimum feature set used by JGit. + */ +class Base64Util { + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte) '='; + + /** Indicates equals sign in encoding. */ + private final static byte EQUALS_SIGN_DEC = -1; + + /** Indicates white space in encoding. */ + private final static byte WHITE_SPACE_DEC = -2; + + /** Indicates an invalid byte during decoding. */ + private final static byte INVALID_DEC = -3; + + /** The 64 valid Base64 values. */ + private final static byte[] ENC; + + /** + * Translates a Base64 value to either its 6-bit reconstruction value or a + * negative number indicating some other meaning. The table is only 7 bits + * wide, as the 8th bit is discarded during decoding. + */ + private final static byte[] DEC; + + static { + try { + ENC = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" // + + "abcdefghijklmnopqrstuvwxyz" // + + "0123456789" // + + "+/" // + ).getBytes("UTF-8"); + } catch (UnsupportedEncodingException uee) { + throw new RuntimeException(uee.getMessage(), uee); + } + + DEC = new byte[128]; + Arrays.fill(DEC, INVALID_DEC); + + for (int i = 0; i < 64; i++) + DEC[ENC[i]] = (byte) i; + DEC[EQUALS_SIGN] = EQUALS_SIGN_DEC; + + DEC['\t'] = WHITE_SPACE_DEC; + DEC['\n'] = WHITE_SPACE_DEC; + DEC['\r'] = WHITE_SPACE_DEC; + DEC[' '] = WHITE_SPACE_DEC; + } + + /** Defeats instantiation. */ + private Base64Util() { + // Suppress empty block warning. + } + + /** + * 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 accommodate + * 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. + * + * @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 + */ + private static void encode3to4(byte[] source, int srcOffset, + int numSigBytes, byte[] destination, int destOffset) { + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte. + + int inBuff = 0; + switch (numSigBytes) { + case 3: + inBuff |= (source[srcOffset + 2] << 24) >>> 24; + //$FALL-THROUGH$ + + case 2: + inBuff |= (source[srcOffset + 1] << 24) >>> 16; + //$FALL-THROUGH$ + + case 1: + inBuff |= (source[srcOffset] << 24) >>> 8; + } + + switch (numSigBytes) { + case 3: + destination[destOffset] = ENC[(inBuff >>> 18)]; + destination[destOffset + 1] = ENC[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ENC[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = ENC[(inBuff) & 0x3f]; + break; + + case 2: + destination[destOffset] = ENC[(inBuff >>> 18)]; + destination[destOffset + 1] = ENC[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = ENC[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = EQUALS_SIGN; + break; + + case 1: + destination[destOffset] = ENC[(inBuff >>> 18)]; + destination[destOffset + 1] = ENC[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = EQUALS_SIGN; + destination[destOffset + 3] = EQUALS_SIGN; + break; + } + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source + * The data to convert + * @return encoded base64 representation of source. + */ + public static String encodeBytes(byte[] source) { + return encodeBytes(source, 0, source.length); + } + + /** + * 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 encoded base64 representation of source. + */ + public static String encodeBytes(byte[] source, int off, int len) { + final int len43 = len * 4 / 3; + + byte[] outBuff = new byte[len43 + ((len % 3) > 0 ? 4 : 0)]; + int d = 0; + int e = 0; + int len2 = len - 2; + + for (; d < len2; d += 3, e += 4) + encode3to4(source, d + off, 3, outBuff, e); + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e); + e += 4; + } + + try { + return new String(outBuff, 0, e, "UTF-8"); + } catch (UnsupportedEncodingException uue) { + return new String(outBuff, 0, e); + } + } + + /** + * Decodes four bytes from array source and writes the resulting + * bytes (up to three of them) 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 accommodate + * srcOffset + 4 for the source array or + * destOffset + 3 for the destination array. This + * method returns the actual number of bytes that were converted from the + * Base64 encoding. + * + * @param source + * the array to convert + * @param srcOffset + * the index where conversion begins + * @param destination + * the array to hold the conversion + * @param destOffset + * the index where output will be put + * @return the number of decoded bytes converted + */ + private static int decode4to3(byte[] source, int srcOffset, + byte[] destination, int destOffset) { + // Example: Dk== + if (source[srcOffset + 2] == EQUALS_SIGN) { + int outBuff = ((DEC[source[srcOffset]] & 0xFF) << 18) + | ((DEC[source[srcOffset + 1]] & 0xFF) << 12); + destination[destOffset] = (byte) (outBuff >>> 16); + return 1; + } + + // Example: DkL= + else if (source[srcOffset + 3] == EQUALS_SIGN) { + int outBuff = ((DEC[source[srcOffset]] & 0xFF) << 18) + | ((DEC[source[srcOffset + 1]] & 0xFF) << 12) + | ((DEC[source[srcOffset + 2]] & 0xFF) << 6); + destination[destOffset] = (byte) (outBuff >>> 16); + destination[destOffset + 1] = (byte) (outBuff >>> 8); + return 2; + } + + // Example: DkLE + else { + int outBuff = ((DEC[source[srcOffset]] & 0xFF) << 18) + | ((DEC[source[srcOffset + 1]] & 0xFF) << 12) + | ((DEC[source[srcOffset + 2]] & 0xFF) << 6) + | ((DEC[source[srcOffset + 3]] & 0xFF)); + + destination[destOffset] = (byte) (outBuff >> 16); + destination[destOffset + 1] = (byte) (outBuff >> 8); + destination[destOffset + 2] = (byte) (outBuff); + + return 3; + } + } + + /** + * Low-level decoding ASCII characters from a byte array. + * + * @param source + * The Base64 encoded data + * @param off + * The offset of where to begin decoding + * @param len + * The length of characters to decode + * @return decoded data + * @throws IllegalArgumentException + * the input is not a valid Base64 sequence. + */ + public static byte[] decode(byte[] source, int off, int len) { + byte[] outBuff = new byte[len * 3 / 4]; // Upper limit on size of output + int outBuffPosn = 0; + + byte[] b4 = new byte[4]; + int b4Posn = 0; + + for (int i = off; i < off + len; i++) { + byte sbiCrop = (byte) (source[i] & 0x7f); + byte sbiDecode = DEC[sbiCrop]; + + if (EQUALS_SIGN_DEC <= sbiDecode) { + b4[b4Posn++] = sbiCrop; + if (b4Posn > 3) { + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn); + b4Posn = 0; + + // If that was the equals sign, break out of 'for' loop + if (sbiCrop == EQUALS_SIGN) + break; + } + + } else if (sbiDecode != WHITE_SPACE_DEC) + throw new IllegalArgumentException(MessageFormat.format( + "Bad Base64 input character at {0} : {1} (decimal)", i, + source[i] & 0xff)); + } + + if (outBuff.length == outBuffPosn) + return outBuff; + + byte[] out = new byte[outBuffPosn]; + System.arraycopy(outBuff, 0, out, 0, outBuffPosn); + return out; + } + + /** + * Decodes data from Base64 notation. + * + * @param s + * the string to decode + * @return the decoded data + */ + public static byte[] decode(String s) { + byte[] bytes; + try { + bytes = s.getBytes("UTF-8"); + } catch (UnsupportedEncodingException uee) { + bytes = s.getBytes(); + } + return decode(bytes, 0, bytes.length); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/utils/CodeFileUtil.java b/app/src/main/java/net/oschina/app/improve/git/utils/CodeFileUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..27e559e78078811aec573cf2eb2f55d537b7ca16 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/utils/CodeFileUtil.java @@ -0,0 +1,167 @@ +package net.oschina.app.improve.git.utils; + +/** + * Created by haibin + * on 2017/3/13. + */ + +public final class CodeFileUtil { + /** + * 判断是否是代码文件 + */ + public static boolean isCodeTextFile(String fileName) { + boolean res = false; + // 文件的后缀 + int index = fileName.lastIndexOf("."); + if (index > 0) { + fileName = fileName.substring(index); + } + String codeFileSuffix[] = new String[] + { + ".java", + ".confg", + ".ini", + ".xml", + ".json", + ".txt", + ".go", + ".php", + ".php3", + ".php4", + ".php5", + ".js", + ".css", + ".html", + ".properties", + ".c", + ".hpp", + ".h", + ".hh", + ".cpp", + ".cfg", + ".rb", + ".example", + ".gitignore", + ".project", + ".classpath", + ".m", + ".md", + ".rst", + ".vm", + ".cl", + ".py", + ".pl", + ".haml", + ".erb", + ".scss", + ".bat", + ".coffee", + ".as", + ".sh", + ".m", + ".pas", + ".cs", + ".groovy", + ".scala", + ".sql", + ".bas", + ".xml", + ".vb", + ".xsl", + ".swift", + ".ftl", + ".yml", + ".ru", + ".jsp", + ".markdown", + ".cshap", + ".apsx", + ".sass", + ".less", + ".ftl", + ".haml", + ".log", + ".tx", + ".csproj", + ".sln", + ".clj", + ".scm", + ".xhml", + ".xaml", + ".lua", + ".sty", + ".cls", + ".thm", + ".tex", + ".bst", + ".config", + "Podfile", + "Podfile.lock", + ".plist", + ".storyboard", + "gradlew", + ".gradle", + ".pro", + ".pbxproj", + ".xcscheme", + ".proto", + ".wxss", + ".wxml", + ".vi", + ".ctl", + ".ts", + ".kt", + ".vue", + ".babelrc" + }; + for (String string : codeFileSuffix) { + if (fileName.equalsIgnoreCase(string)) { + res = true; + } + } + + // 特殊的文件 + String fileNames[] = new String[] + { + "LICENSE", "TODO", "README", "readme", "makefile", "gemfile", "gemfile.*", "gemfile.lock", "CHANGELOG" + }; + + for (String string : fileNames) { + if (fileName.equalsIgnoreCase(string)) { + res = true; + } + } + + return res; + } + + + /** + * 判断是否是图片 + */ + public static boolean isImage(String fileName) { + boolean res = false; + // 图片后缀 + int index = fileName.lastIndexOf("."); + if (index > 0) { + fileName = fileName.substring(index); + } + String imageSuffix[] = new String[] + { + ".png", ".jpg", ".jpeg", + ".jpe", ".bmp", ".exif", + ".dxf", ".wbmp", ".ico", + ".gif", ".pcx", ".hdri", + ".fpx", ".ufo", ".tiff", + ".svg", ".eps", ".ai", + ".tga", ".pcd", + }; + for (String string : imageSuffix) { + if (fileName.equalsIgnoreCase(string)) { + res = true; + } + } + return res; + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/git/utils/EncodingUtils.java b/app/src/main/java/net/oschina/app/improve/git/utils/EncodingUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..498d74c26a617e0888acce3f2cff4194615e1e33 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/utils/EncodingUtils.java @@ -0,0 +1,46 @@ +package net.oschina.app.improve.git.utils; + + +import java.io.UnsupportedEncodingException; + +/** + * Encoding utilities + */ +public abstract class EncodingUtils { + + /** + * Decode base64 encoded string + * + * @param content + * @return byte array + */ + public static final byte[] fromBase64(final String content) { + return Base64Util.decode(content); + } + + /** + * Base64 encode given byte array + * + * @param content + * @return byte array + */ + public static final String toBase64(final byte[] content) { + return Base64Util.encodeBytes(content); + } + + /** + * Base64 encode given byte array + * + * @param content + * @return byte array + */ + public static final String toBase64(final String content) { + byte[] bytes; + try { + bytes = content.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + bytes = content.getBytes(); + } + return toBase64(bytes); + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/git/utils/MarkdownUtils.java b/app/src/main/java/net/oschina/app/improve/git/utils/MarkdownUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..023701f4a56d79c823bf13f1205ccb0943f18f89 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/utils/MarkdownUtils.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013 GitHub 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 net.oschina.app.improve.git.utils; + +import static java.util.Locale.US; +import android.text.TextUtils; + +/** + * Utilities for dealing with Markdown files + */ +public class MarkdownUtils { + + private static final String[] MARKDOWN_EXTENSIONS = { ".md", ".mkdn", + ".mdwn", ".mdown", ".markdown", ".mkd", ".mkdown", ".ron" }; + + /** + * Is the the given file name a Markdown file? + * + * @param name + * @return true if the name has a markdown extension, false otherwise + */ + public static boolean isMarkdown(String name) { + if (TextUtils.isEmpty(name)) + return false; + + name = name.toLowerCase(US); + for (String extension : MARKDOWN_EXTENSIONS) + if (name.endsWith(extension)) + return true; + + return false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/git/utils/SourceEditor.java b/app/src/main/java/net/oschina/app/improve/git/utils/SourceEditor.java new file mode 100644 index 0000000000000000000000000000000000000000..a6ba5b79f7638b760a2795d1e6a7e2e61d6ff259 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/git/utils/SourceEditor.java @@ -0,0 +1,212 @@ +/* + * Copyright 2012 GitHub 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 net.oschina.app.improve.git.utils; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.os.Build; +import android.text.TextUtils; +import android.webkit.JavascriptInterface; +import android.webkit.WebSettings; +import android.webkit.WebView; + +import net.oschina.app.improve.git.bean.CodeDetail; + +import java.io.UnsupportedEncodingException; + + +/** + * 用markdown渲染代码显示 + * + * @author 火蚁(http://my.oschina.net/LittleDY) + */ +@TargetApi(Build.VERSION_CODES.HONEYCOMB) +@SuppressLint("SetJavaScriptEnabled") +public class SourceEditor { + + private final WebView view; + + private boolean wrap; + + private String name; + + private String content; + + private boolean encoded; + + private boolean markdown; + + /** + * Create source editor using given web view + * + * @param view + */ + @SuppressLint("AddJavascriptInterface") + public SourceEditor(final WebView view) { + WebSettings settings = view.getSettings(); + settings.setJavaScriptEnabled(true); + settings.setBuiltInZoomControls(true); + try { + int version = Build.VERSION.SDK_INT; + if (version >= 11) { + // 这个方法在API level 11 以上才可以调用,不然会发生异常 + settings.setDisplayZoomControls(false); + } + } catch (NumberFormatException e) { + + } + settings.setUseWideViewPort(true); + view.addJavascriptInterface(SourceEditor.this, "SourceEditor"); + this.view = view; + } + + /** + * @return name + */ + @JavascriptInterface + public String getName() { + return name; + } + + /** + * @return content + */ + @JavascriptInterface + public String getRawContent() { + return content; + } + + /** + * @return content + */ + @JavascriptInterface + public String getContent() { + if (encoded) + try { + return new String(EncodingUtils.fromBase64(content), + "UTF-8"); + } catch (UnsupportedEncodingException e) { + return getRawContent(); + } + else + return getRawContent(); + } + + /** + * @return wrap + */ + @JavascriptInterface + public boolean getWrap() { + return wrap; + } + + /** + * @return markdown + */ + @JavascriptInterface + public boolean isMarkdown() { + return markdown; + } + + /** + * Set whether lines should wrap + * + * @param wrap + * @return this editor + */ + @JavascriptInterface + public SourceEditor setWrap(final boolean wrap) { + this.wrap = wrap; + loadSource(); + return this; + } + + /** + * Sets whether the content is a markdown file + * + * @param markdown + * @return this editor + */ + @JavascriptInterface + public SourceEditor setMarkdown(final boolean markdown) { + this.markdown = markdown; + return this; + } + + /** + * Bind content to current {@link WebView} + * + * @param name + * @param content + * @param encoded + * @return this editor + */ + @JavascriptInterface + public SourceEditor setSource(final String name, final String content, + final boolean encoded) { + this.name = name; + this.content = content; + this.encoded = encoded; + loadSource(); + + return this; + } + + @JavascriptInterface + private void loadSource() { + if (name != null && content != null) { + view.loadUrl("file:///android_asset/source-editor.html"); + } + } + + /** + * Bind blob content to current {@link WebView} + * + * @param name + * @param blob + * @return this editor + */ + @JavascriptInterface + public SourceEditor setSource(final String name, final CodeDetail blob) { + + String content = blob.getContent(); + if (content == null) + content = ""; + boolean encoded = !TextUtils.isEmpty(content) + && CodeDetail.ENCODING_BASE64.equals(blob.getEncoding()); + return setSource(name, content, encoded); + } + + /** + * Toggle line wrap + * + * @return this editor + */ + @JavascriptInterface + public SourceEditor toggleWrap() { + return setWrap(!wrap); + } + + /** + * Toggle markdown file rendering + * + * @return this editor + */ + @JavascriptInterface + public SourceEditor toggleMarkdown() { + return setMarkdown(!markdown); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/BrowserActivity.java b/app/src/main/java/net/oschina/app/improve/main/BrowserActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..28950c49520876d4a8d03db93ec84663c12d1a81 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/BrowserActivity.java @@ -0,0 +1,45 @@ +package net.oschina.app.improve.main; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.widget.OWebView; + +import butterknife.Bind; + +public class BrowserActivity extends BaseActivity { + @Bind(R.id.webView) + protected OWebView mWebView; + + @Override + protected int getContentView() { + return R.layout.activity_browser; + } + + @Override + public void onResume() { + super.onResume(); + OWebView webView = mWebView; + if (webView != null) { + webView.onResume(); + } + } + + @Override + public void onPause() { + super.onPause(); + OWebView webView = mWebView; + if (webView != null) { + webView.onPause(); + } + } + + @Override + public void onDestroy() { + OWebView view = mWebView; + if (view != null) { + mWebView = null; + view.destroy(); + } + super.onDestroy(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/ClipManager.java b/app/src/main/java/net/oschina/app/improve/main/ClipManager.java new file mode 100644 index 0000000000000000000000000000000000000000..2bec49d5216d835ceea5b0055f229d848457af4b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/ClipManager.java @@ -0,0 +1,123 @@ +package net.oschina.app.improve.main; + +import android.content.ClipboardManager; +import android.content.Context; +import android.text.TextUtils; + +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.main.update.OSCSharedPreference; + +import java.util.regex.Pattern; + +/** + * 剪切版监听 + * Created by huanghaibin on 2017/12/1. + */ + +public final class ClipManager { + + public static boolean IS_SYSTEM_URL = false; + private static OnClipChangeListener mListener; + private static ClipboardManager mManager; + private static ClipboardManager.OnPrimaryClipChangedListener mChangeListener; + private static String mUrl; + + public static void register(Context context, OnClipChangeListener listener) { + mManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + mListener = listener; + if (mChangeListener == null) { + mChangeListener = new ClipboardManager.OnPrimaryClipChangedListener() { + @Override + public void onPrimaryClipChanged() { + if (mManager == null) + return; + try { + if (mManager.hasPrimaryClip() && mManager.getPrimaryClip().getItemCount() > 0) { + CharSequence addedText = mManager.getPrimaryClip().getItemAt(0).getText(); + if (addedText != null && mListener != null && checkUrl(addedText.toString())) { + if (BaseActivity.IS_ACTIVE) { + if (OSCSharedPreference.getInstance().isRelateClip() && !IS_SYSTEM_URL) { + OSCSharedPreference.getInstance().putLastShareUrl(mUrl); + mListener.onClipChange(addedText.toString()); + mUrl = null; + } + } else { + mUrl = addedText.toString(); + } + } else { + mUrl = null; + } + } + } catch (Exception e) { + e.printStackTrace(); + mUrl = null; + } + + } + }; + } + if (mManager == null) + return; + mManager.addPrimaryClipChangedListener(mChangeListener); + String url = getClipText(); + if (checkUrl(url) && !url.equals(OSCSharedPreference.getInstance().getLastShareUrl())) { + OSCSharedPreference.getInstance().putLastShareUrl(url); + mListener.onClipChange(getClipText()); + } + } + + @SuppressWarnings("unused") + static void unregister() { + mListener = null; + if (mManager == null || mChangeListener == null) + return; + mManager.removePrimaryClipChangedListener(mChangeListener); + mManager = null; + mChangeListener = null; + } + + public static void onResume() { + if (mManager == null || TextUtils.isEmpty(mUrl) || mListener == null) + return; + if (OSCSharedPreference.getInstance().isRelateClip()) { + OSCSharedPreference.getInstance().putLastShareUrl(mUrl); + mListener.onClipChange(mUrl); + } + mUrl = null; + } + + public interface OnClipChangeListener { + void onClipChange(String url); + } + + private static boolean checkUrl(String url) { + if (TextUtils.isEmpty(url)) { + return false; + } + Pattern pattern = Pattern.compile("^https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]"); + return pattern.matcher(url).find(); + } + + private static String getClipText() { + try { + if (mManager != null && mManager.hasPrimaryClip() && mManager.getPrimaryClip().getItemCount() > 0) { + CharSequence addedText = mManager.getPrimaryClip().getItemAt(0).getText(); + if (addedText != null) { + return addedText.toString(); + } + } + return ""; + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + public static String getClipUrl() { + String text = getClipText(); + if (checkUrl(text)) { + return text; + } + return ""; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/ErrorActivity.java b/app/src/main/java/net/oschina/app/improve/main/ErrorActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..fa6dc160ef2909225df7ed9c97c382d58e84ed51 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/ErrorActivity.java @@ -0,0 +1,55 @@ +package net.oschina.app.improve.main; + +import android.content.Context; +import android.content.Intent; +import android.view.View; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseBackActivity; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 异常信息界面 + * Created by haibin on 2017/5/8. + */ + +public class ErrorActivity extends BaseBackActivity implements View.OnClickListener { + @Bind(R.id.tv_crash_info) + TextView mTextCrashInfo; + + public static void show(Context context, String message) { + if (message == null) + return; + Intent intent = new Intent(context, ErrorActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | + Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("message", message); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_error; + } + + @Override + protected void initData() { + super.initData(); + mTextCrashInfo.setText(getIntent().getStringExtra("message")); + } + + @OnClick({R.id.btn_restart}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_restart: + Intent intent = new Intent(this, MainActivity.class); + startActivity(intent); + finish(); + break; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/ExploreFragment.java b/app/src/main/java/net/oschina/app/improve/main/ExploreFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..48d9123584dea891d4476590f98443f2d89eb8fa --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/ExploreFragment.java @@ -0,0 +1,140 @@ +package net.oschina.app.improve.main; + +import android.view.View; +import android.widget.ImageView; + +import net.oschina.app.R; +import net.oschina.app.Setting; +import net.oschina.app.bean.SimpleBackPage; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.bean.SubTab; +import net.oschina.app.improve.detail.SubActivity; +import net.oschina.app.improve.git.feature.FeatureActivity; +import net.oschina.app.improve.git.gist.GistActivity; +import net.oschina.app.improve.main.discover.ShakePresentActivity; +import net.oschina.app.improve.main.synthesize.web.ZBWebActivity; +import net.oschina.app.improve.nearby.NearbyActivity; +import net.oschina.app.improve.search.v2.SearchActivity; +import net.oschina.app.interf.OnTabReselectListener; +import net.oschina.app.util.UIHelper; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 发现界面 + * Created by huanghaibin on 2017/10/23. + */ + +public class ExploreFragment extends BaseFragment implements View.OnClickListener, OnTabReselectListener { + + @Bind(R.id.iv_has_location) + ImageView mIvLocated; + @Bind(R.id.viewStatusBar) + View mStatusBar; + + @Override + protected int getLayoutId() { + return R.layout.fragment_explore_v2; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + //setStatusBarPadding(); + if (BaseActivity.hasSetStatusBarColor) { + mStatusBar.setBackgroundColor(getResources().getColor(R.color.status_bar_color)); + } + } + + @Override + public void onResume() { + super.onResume(); + hasLocation(); + } + + @OnClick({R.id.rl_git, R.id.rl_gits, + R.id.btn_search,R.id.rl_zb, + R.id.rl_soft, R.id.rl_scan, + R.id.rl_shake, R.id.layout_events, + R.id.layout_nearby}) + @Override + public void onClick(View v) { + int id = v.getId(); + switch (id) { + case R.id.btn_search: + //SearchActivity.show(getActivity()); + SearchActivity.show(mContext); + break; + case R.id.rl_zb: + ZBWebActivity.show(mContext,"https://zb.oschina.net/projects/list.html"); + break; + case R.id.rl_git: + FeatureActivity.show(getActivity()); + break; + case R.id.rl_gits: + GistActivity.show(mContext); + break; + case R.id.rl_soft: //开源软件 + UIHelper.showSimpleBack(getActivity(), + SimpleBackPage.OPEN_SOURCE_SOFTWARE); + break; + case R.id.rl_scan: //扫一扫 + UIHelper.showScanActivity(getActivity()); + break; + case R.id.rl_shake: //摇一摇 + showShake(); + break; + case R.id.layout_events: //线下活动 + SubTab tab = new SubTab(); + + SubTab.Banner banner = tab.new Banner(); + banner.setCatalog(3); + banner.setHref("https://www.oschina.net/action/apiv2/banner?catalog=3"); + tab.setBanner(banner); + + tab.setName("线下活动"); + tab.setFixed(false); + tab.setHref("https://www.oschina.net/action/apiv2/sub_list?token=727d77c15b2ca641fff392b779658512"); + tab.setNeedLogin(false); + tab.setSubtype(1); + tab.setOrder(74); + tab.setToken("727d77c15b2ca641fff392b779658512"); + tab.setType(5); + + SubActivity.show(mContext, tab); + break; + case R.id.layout_nearby: + if (!AccountHelper.isLogin()) { + LoginActivity.show(getContext()); + break; + } + //NearbyActivity.show(getContext()); + NearbyActivity.show(mContext); + break; + default: + break; + } + } + + @Override + public void onTabReselect() { + hasLocation(); + } + + private void showShake() { + ShakePresentActivity.show(getActivity()); + } + + private void hasLocation() { + boolean hasLocation = Setting.hasLocation(getContext()); + if (hasLocation) { + mIvLocated.setVisibility(View.VISIBLE); + } else { + mIvLocated.setVisibility(View.GONE); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/FeedBackActivity.java b/app/src/main/java/net/oschina/app/improve/main/FeedBackActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..bdfc99e8a8f2d5f94036edfcc0ed437dd4dce22d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/FeedBackActivity.java @@ -0,0 +1,208 @@ +package net.oschina.app.improve.main; + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.RadioButton; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.Message; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.media.SelectImageActivity; +import net.oschina.app.improve.media.config.SelectOptions; +import net.oschina.app.improve.utils.PicturesCompressor; + +import java.io.File; +import java.lang.reflect.Type; + +import butterknife.Bind; +import butterknife.OnClick; +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2016/10/14. + */ + +public class FeedBackActivity extends BackActivity implements View.OnClickListener { + + @Bind(R.id.rb_error) + RadioButton rb_error; + + @Bind(R.id.et_feed_back) + EditText et_feed_back; + + @Bind(R.id.iv_add) + ImageView iv_add; + + @Bind(R.id.iv_clear_img) + ImageView iv_clear_img; + + private String mFilePath = ""; + private ProgressDialog mDialog; + + public static void show(Context context) { + context.startActivity(new Intent(context, FeedBackActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_feed_back; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + rb_error.setChecked(true); + } + + @Override + protected void initData() { + super.initData(); + } + + @OnClick({R.id.iv_add, R.id.iv_clear_img}) + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.btn_commit: + String content = getFeedBackContent(); + if (TextUtils.isEmpty(content) && TextUtils.isEmpty(mFilePath)) { + return; + } + String mFeedbackStr = "[Android-主站-%s]"; + content = String.format(mFeedbackStr, rb_error.isChecked() ? "程序错误" : "功能建议") + content; + File file = new File(mFilePath); + if (file.exists()) { + compress(mFilePath, new Run(content)); + } else { + addFeedBack(content, null); + } + break; + case R.id.iv_add: + openImageSelector(); + break; + case R.id.iv_clear_img: + iv_add.setImageResource(R.mipmap.ic_tweet_add); + iv_clear_img.setVisibility(View.GONE); + mFilePath = ""; + break; + } + } + + public void openImageSelector() { + SelectImageActivity.show(this, new SelectOptions.Builder() + .setHasCam(false) + .setSelectCount(1) + .setCallback(new SelectOptions.Callback() { + @Override + public void doSelected(String[] images) { + mFilePath = images[0]; + getImageLoader().load(mFilePath).into(iv_add); + iv_clear_img.setVisibility(View.VISIBLE); + } + }).build()); + } + + /** + * 添加反馈,走系统私信接口 + * + * @param content content + * @param file file + */ + private void addFeedBack(String content, File file) { + getDialog().show(); + OSChinaApi.pubMessage(2609904, content, file, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + Toast.makeText(FeedBackActivity.this, "网络错误,请重试", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + Type type = new TypeToken>() { + }.getType(); + try { + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + Toast.makeText(FeedBackActivity.this, "谢谢您的反馈", Toast.LENGTH_SHORT).show(); + finish(); + } else { + Toast.makeText(FeedBackActivity.this, resultBean.getMessage(), Toast.LENGTH_SHORT).show(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onFinish() { + super.onFinish(); + mDialog.dismiss(); + } + }); + } + + private void compress(final String oriPath, final Run runnable) { + final String path = getFilesDir() + "/message/" + getFileName(oriPath); + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + if (PicturesCompressor.compressImage(oriPath, path, 512 * 1024)) { + runnable.setPath(path); + runOnUiThread(runnable); + } + } + }); + } + + private String getFileName(String filePath) { + return filePath.substring(filePath.lastIndexOf("/") + 1); + } + + private class Run implements Runnable { + private String path; + private String content; + + Run(String content) { + this.content = content; + } + + @Override + public void run() { + File file = new File(path); + addFeedBack(content, file); + } + + public void setPath(String path) { + this.path = path; + } + } + + public String getFeedBackContent() { + return et_feed_back.getText().toString().trim(); + } + + public ProgressDialog getDialog() { + if (mDialog == null) { + mDialog = new ProgressDialog(this); + mDialog.setCancelable(false); + mDialog.setCanceledOnTouchOutside(false); + } + mDialog.setMessage("反馈中..."); + return mDialog; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/MainActivity.java b/app/src/main/java/net/oschina/app/improve/main/MainActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..2130edb8ae64d29adfbc4cff7e18f4109ac90bf0 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/MainActivity.java @@ -0,0 +1,570 @@ +package net.oschina.app.improve.main; + +import android.Manifest; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.os.SystemClock; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.text.TextUtils; +import android.view.View; +import android.view.animation.LinearInterpolator; +import android.widget.LinearLayout; +import android.widget.Toast; + +import com.baidu.location.BDLocation; +import com.baidu.location.LocationClient; +import com.baidu.location.LocationClientOption; +import com.baidu.mapapi.model.LatLng; +import com.baidu.mapapi.radar.RadarSearchError; +import com.baidu.mapapi.radar.RadarSearchManager; +import com.baidu.mapapi.radar.RadarUploadInfo; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppConfig; +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.Setting; +import net.oschina.app.base.BaseApplication; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.Version; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.detail.db.API; +import net.oschina.app.improve.detail.db.Behavior; +import net.oschina.app.improve.detail.db.DBManager; +import net.oschina.app.improve.main.location.BDLocationAdapter; +import net.oschina.app.improve.main.location.RadarSearchAdapter; +import net.oschina.app.improve.main.nav.NavFragment; +import net.oschina.app.improve.main.nav.NavigationButton; +import net.oschina.app.improve.main.synthesize.pub.PubTipActivity; +import net.oschina.app.improve.main.update.CheckUpdateManager; +import net.oschina.app.improve.main.update.DownloadService; +import net.oschina.app.improve.notice.NoticeManager; +import net.oschina.app.improve.search.activities.NearbyActivity; +import net.oschina.app.improve.tweet.service.TweetNotificationManager; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.interf.OnTabReselectListener; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; + +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Type; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +import static net.oschina.app.improve.search.activities.NearbyActivity.LOCATION_PERMISSION; + +public class MainActivity extends BaseActivity implements NavFragment.OnNavigationReselectListener, + EasyPermissions.PermissionCallbacks, CheckUpdateManager.RequestPermissions { + + public static boolean IS_SHOW = false; + private static final int RC_EXTERNAL_STORAGE = 0x04;//存储权限 + public static final String ACTION_NOTICE = "ACTION_NOTICE"; + private long mBackPressedTime; + + + private Version mVersion; + + @Bind(R.id.activity_main_ui) + LinearLayout mMainUi; + + private NavFragment mNavBar; + private List mTurnBackListeners = new ArrayList<>(); + private RadarSearchManager mRadarSearchManager; + private LocationClient mLocationClient; + private RadarSearchAdapter mRadarSearchAdapter; + + + public static void show(Context context) { + IS_SHOW = true; + context.startActivity(new Intent(context, MainActivity.class)); + } + + public interface TurnBackListener { + boolean onTurnBack(); + } + + @Override + protected int getContentView() { + return R.layout.activity_main_ui; + } + + @Override + protected void initWidget() { + super.initWidget(); + IS_SHOW = true; + setSwipeBackEnable(false); + setStatusBarDarkMode(); + FragmentManager manager = getSupportFragmentManager(); + mNavBar = ((NavFragment) manager.findFragmentById(R.id.fag_nav)); + mNavBar.setup(this, manager, R.id.main_container, this); + + + // TODO: 2017/11/8 隐藏订阅 +// if (AppContext.get("isFirstComing", true)) { +// View view = findViewById(R.id.layout_ripple); +// view.setVisibility(View.VISIBLE); +// view.setOnClickListener(new View.OnClickListener() { +// @Override +// public void onClick(View v) { +// ((ViewGroup) v.getParent()).removeView(v); +// AppContext.set("isFirstComing", false); +// } +// }); +// } + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + doNewIntent(getIntent(), true); + } + + @Override + protected void onPostCreate(@Nullable Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + try { + //如果是两天前的数据,则全部上传 + if (!AccountHelper.isLogin()) + return; + String updateTime = AppConfig.getAppConfig(this).get("upload_behavior_time"); + if (DBManager.getInstance().getCount(Behavior.class) >= 15 && + !TextUtils.isEmpty(updateTime) && + (System.currentTimeMillis() - StringUtils.toDate(updateTime).getTime() >= 172800000)) { + final List behaviors = DBManager.getInstance() + .get(Behavior.class); + API.addBehaviors(new Gson().toJson(behaviors), new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean.isSuccess()) { + //清楚数据,避免清空没有上传的数据 + DBManager.getInstance() + .delete(Behavior.class, "id<=?", String.valueOf(behaviors.get(behaviors.size() - 1).getId())); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + doNewIntent(intent, false); + } + + @SuppressWarnings("unused") + private void doNewIntent(Intent intent, boolean isCreate) { + if (intent == null || intent.getAction() == null) + return; + String action = intent.getAction(); + if (action.equals(ACTION_NOTICE)) { + NavFragment bar = mNavBar; + if (bar != null) { + bar.select(3); + } + } + } + + @Override + public void onReselect(NavigationButton navigationButton) { + Fragment fragment = navigationButton.getFragment(); + if (fragment != null + && fragment instanceof OnTabReselectListener) { + OnTabReselectListener listener = (OnTabReselectListener) fragment; + listener.onTabReselect(); + } + } + + @Override + protected void initData() { + super.initData(); + NoticeManager.init(this); + // in this we can checkShare update + checkUpdate(); + checkLocation(); + TweetNotificationManager.setup(this); + + ClipManager.register(this, new ClipManager.OnClipChangeListener() { + @Override + public void onClipChange(String url) { + PubTipActivity.show(MainActivity.this, url); + } + }); + } + + private void checkLocation() { + + //首先判断appCode是否存在,如果存在是否大于当前版本的appCode,或者第一次全新安装(默认0)表示没有保存appCode + int hasLocationAppCode = Setting.hasLocationAppCode(getApplicationContext()); + int versionCode = TDevice.getVersionCode(); + if ((hasLocationAppCode <= 0) || (hasLocationAppCode > versionCode)) { + //如果是登陆状态,直接进行位置信息定位并上传 + if (AccountHelper.isLogin()) { + //当app第一次被安装时,不管是覆盖安装(不管是否有定位权限)还是全新安装都必须进行定位请求 + Setting.updateLocationAppCode(getApplicationContext(), versionCode); + requestLocationPermission(); + } + return; + } + + //如果有账户登陆,并且有主动上传过位置信息。那么准备请求定位 + if (AccountHelper.isLogin() && Setting.hasLocation(getApplicationContext())) { + + //1.有主动授权过,直接进行定位,否则不进行操作任何操作 + if (Setting.hasLocationPermission(getApplicationContext())) { + requestLocationPermission(); + } + } + } + + @Override + public void call(Version version) { + this.mVersion = version; + requestExternalStorage(); + } + + @AfterPermissionGranted(RC_EXTERNAL_STORAGE) + public void requestExternalStorage() { + if (EasyPermissions.hasPermissions(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { + DownloadService.startService(this, mVersion.getDownloadUrl()); + } else { + EasyPermissions.requestPermissions(this, "", RC_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE); + } + } + + + /** + * proxy request permission + */ + @AfterPermissionGranted(NearbyActivity.LOCATION_PERMISSION) + private void requestLocationPermission() { + if (EasyPermissions.hasPermissions(this, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.READ_PHONE_STATE)) { + startLbs(); + } else { + EasyPermissions.requestPermissions(this, getString(R.string.need_lbs_permission_hint), LOCATION_PERMISSION, + Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.READ_PHONE_STATE); + } + } + + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + + for (String perm : perms) { + if (perm.equals(Manifest.permission.READ_EXTERNAL_STORAGE)) { + + DialogHelper.getConfirmDialog(this, "温馨提示", "需要开启开源中国对您手机的存储权限才能下载安装,是否现在开启", "去开启", "取消", true, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + } + }, null).show(); + + } else { + Setting.updateLocationPermission(getApplicationContext(), false); + } + } + + + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + IS_SHOW = false; + NoticeManager.stopListen(this); + releaseLbs(); + ClipManager.unregister(); + } + + public void addOnTurnBackListener(TurnBackListener l) { + this.mTurnBackListeners.add(l); + } + + public void toggleNavTabView(boolean isShowOrHide) { + final View view = mNavBar.getView(); + if (view == null) return; + // hide + view.setVisibility(View.VISIBLE); + if (!isShowOrHide) { + view.animate() + .translationY(view.getHeight()) + .setDuration(180) + .setInterpolator(new LinearInterpolator()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + view.setTranslationY(view.getHeight()); + view.setVisibility(View.GONE); + } + }); + } else { + view.animate() + .translationY(0) + .setDuration(180) + .setInterpolator(new LinearInterpolator()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + // fix:bug > 点击隐藏的同时,快速点击显示 + view.setVisibility(View.VISIBLE); + view.setTranslationY(0); + } + }); + } + } + + @Override + public void onBackPressed() { + for (TurnBackListener l : mTurnBackListeners) { + if (l.onTurnBack()) return; + } + boolean isDoubleClick = BaseApplication.get(AppConfig.KEY_DOUBLE_CLICK_EXIT, true); + if (isDoubleClick) { + long curTime = SystemClock.uptimeMillis(); + if ((curTime - mBackPressedTime) < (3 * 1000)) { + finish(); + } else { + mBackPressedTime = curTime; + Toast.makeText(this, R.string.tip_double_click_exit, Toast.LENGTH_LONG).show(); + } + } else { + finish(); + } + } + + private void checkUpdate() { + if (!AppContext.get(AppConfig.KEY_CHECK_UPDATE, true)) { + return; + } + CheckUpdateManager manager = new CheckUpdateManager(this, false); + manager.setCaller(this); + manager.checkUpdate(false); + } + + /** + * start auto lbs service + */ + private void startLbs() { + if (mRadarSearchManager == null || mLocationClient == null) { + initLbs(); + } + //进行定位 + mLocationClient.start(); + } + + /** + * init lbs service + */ + private void initLbs() { + if (mRadarSearchManager == null) { + mRadarSearchManager = RadarSearchManager.getInstance(); + mRadarSearchManager.addNearbyInfoListener(this.mRadarSearchAdapter = new RadarSearchAdapter() { + @Override + public void onGetUploadState(RadarSearchError radarSearchError) { + super.onGetUploadState(radarSearchError); + //上传成功,更新用户本地定位信息标示 + if (radarSearchError == RadarSearchError.RADAR_NO_ERROR) { + Setting.updateLocationInfo(getApplicationContext(), true); + } else { + Setting.updateLocationInfo(getApplicationContext(), false); + } + //不管是否上传成功,都主动释放lbs资源 + releaseLbs(); + } + }); + } + + if (mLocationClient == null) { + mLocationClient = new LocationClient(this); + mLocationClient.registerLocationListener(new BDLocationAdapter() { + @Override + public void onReceiveLocation(BDLocation bdLocation) { + super.onReceiveLocation(bdLocation); + //处理返回的定位信息,进行用户位置信息上传 + ReceiveLocation(bdLocation); + } + }); + } + + LocationClientOption option = new LocationClientOption(); + + //可选,默认高精度,设置定位模式,高精度,低功耗,仅设备 + option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy); + + //可选,默认gcj02,设置返回的定位结果坐标系 + option.setCoorType("bd09ll"); + + //根据网络情况和gps进行准确定位,只定位一次 当获取到真实有效的经纬度时,主动关闭定位功能 + option.setScanSpan(0); + + //可选,设置是否需要地址信息,默认不需要 + option.setIsNeedAddress(false); + + //设置是否需要位置语义化结果 + option.setIsNeedLocationDescribe(false); + + //可选,默认true,定位SDK内部是一个SERVICE,并放到了独立进程,设置是否在stop的时候杀死这个进程,默认不杀死 + //option.setIgnoreKillProcess(false); + + //可选,默认false,设置是否收集CRASH信息,默认收集 + option.SetIgnoreCacheException(false); + + mLocationClient.setLocOption(option); + } + + /** + * 定位回调处理 + * + * @param location location + */ + private void ReceiveLocation(BDLocation location) { + + final int code = location.getLocType(); + switch (code) { + case BDLocation.TypeCriteriaException://62 + releaseLbs(); + return; + case BDLocation.TypeNetWorkException://63 + releaseLbs(); + return; + case BDLocation.TypeServerError://167 + releaseLbs(); + return; + case BDLocation.TypeNetWorkLocation://161 网络定位模式 + break; + case BDLocation.TypeOffLineLocation://66 离线模式 + + if (!TDevice.hasInternet()) { + SimplexToast.show(this, getString(R.string.tip_network_error)); + releaseLbs(); + return; + } + + break; + } + + if (code >= 501) {//非法key + releaseLbs(); + return; + } + + //定位成功,网络ok,主动上传用户位置信息 + if (TDevice.hasInternet() && location.getLatitude() != 4.9E-324 && location.getLongitude() != 4.9E-324) { + + LatLng userLatLng = new LatLng(location.getLatitude(), location.getLongitude()); + + Setting.updateLocationPermission(getApplicationContext(), true); + + //周边雷达设置用户身份标识,id为空默认是设备标识 + String userId = null; + + //上传位置 + RadarUploadInfo info = new RadarUploadInfo(); + + if (AccountHelper.isLogin()) { + userId = String.valueOf(AccountHelper.getUserId()); + + User user = AccountHelper.getUser(); + try { + String company = ""; + if (user.getMore() != null) { + company = user.getMore().getCompany(); + } + company = TextUtils.isEmpty(company) ? "" : company; + String comments = String.format( + "{" + + "\"id\":\"%s\"," + + "\"name\":\"%s\"," + + "\"portrait\":\"%s\"," + + "\"gender\":\"%s\"," + + "\"more\":{" + + "\"company\":\"%s\"}" + + "}" + , user.getId(), user.getName(), user.getPortrait(), user.getGender(), company); + comments = comments.replaceAll("[\\s\n]+", ""); + comments = URLEncoder.encode(comments, NearbyActivity.CHARSET); + info.comments = comments; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + SimplexToast.show(this, getString(R.string.upload_lbs_info_hint)); + } + + } + + mRadarSearchManager.setUserID(userId); + info.pt = userLatLng; + mRadarSearchManager.uploadInfoRequest(info); + } else { + //返回的位置信息异常或网络有问题,即定位失败,停止定位功能,并释放lbs资源 + releaseLbs(); + } + } + + /** + * release lbs source + */ + private void releaseLbs() { + if (mLocationClient != null && mLocationClient.isStarted()) + mLocationClient.stop(); + mLocationClient = null; + //移除监听 + if (mRadarSearchManager != null) { + mRadarSearchManager.removeNearbyInfoListener(mRadarSearchAdapter); + //释放资源 + mRadarSearchManager.destroy(); + mRadarSearchManager = null; + } + } + + @Override + protected boolean isSetStatusBarColor() { + return false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/OnDoubleTouchListener.java b/app/src/main/java/net/oschina/app/improve/main/OnDoubleTouchListener.java new file mode 100644 index 0000000000000000000000000000000000000000..2ddf1bab5286a5ef4bb37ddb770d99dc8dd5603e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/OnDoubleTouchListener.java @@ -0,0 +1,63 @@ +package net.oschina.app.improve.main; + +import android.os.Handler; +import android.os.Looper; +import android.view.MotionEvent; +import android.view.View; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 双击 + * Created by huanghaibin on 2018/1/17. + */ + +public abstract class OnDoubleTouchListener implements View.OnTouchListener { + private long lastTouchTime = 0; + private AtomicInteger touchCount = new AtomicInteger(0); + private Runnable mRun = null; + private Handler mHandler; + + public OnDoubleTouchListener() { + mHandler = new Handler(Looper.getMainLooper()); + } + + private void removeCallback() { + if (mRun != null) { + mHandler.removeCallbacks(mRun); + mRun = null; + } + } + + @Override + public boolean onTouch(final View v, final MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + final long now = System.currentTimeMillis(); + lastTouchTime = now; + + touchCount.incrementAndGet(); + removeCallback(); + + mRun = new Runnable() { + @Override + public void run() { + if (now == lastTouchTime) { + onMultiTouch(v, event, touchCount.get()); + touchCount.set(0); + } + } + }; + + mHandler.postDelayed(mRun, getMultiTouchInterval()); + } + return true; + } + + + private int getMultiTouchInterval() { + return 400; + } + + + public abstract void onMultiTouch(View v, MotionEvent event, int touchCount); +} diff --git a/app/src/main/java/net/oschina/app/improve/main/SchemeUrlActivity.java b/app/src/main/java/net/oschina/app/improve/main/SchemeUrlActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..24cf13297d0cebad167d69cdcb3f35b27c2887c5 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/SchemeUrlActivity.java @@ -0,0 +1,74 @@ +package net.oschina.app.improve.main; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseBackActivity; +import net.oschina.app.improve.tweet.activities.TweetDetailActivity; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.util.UIHelper; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * web url 计划 activity + * Created by huanghaibin_dev + * on 2016/7/11. + */ + +public class SchemeUrlActivity extends BaseBackActivity { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + try { + Intent intent = getIntent(); + if (intent != null) { + // oscapp://www.oschina.net/launch/app?type=-1(&id=112213) + String metaData = intent.getDataString(); + long id = 0; + int type = 0; + if (metaData != null) { + Pattern pattern = Pattern.compile("([^&?]*)"); + Matcher matcher = pattern.matcher(metaData); + while (matcher.find()) { + String group = matcher.group(); + if (!group.contains("www.oschina.net")) { + String[] param = group.split("="); + if (group.contains("type")) { + type = Integer.parseInt(param[1]); + } else if (group.contains("id")) { + id = Long.parseLong(param[1]); + } + } + } + + switch (type) { + case 20://打开个人中心 + OtherUserHomeActivity.show(this, id); + break; + case 100://打开动弹详情 + TweetDetailActivity.show(this, id); + break; + default: + if (id == 0)//默认启动首页 + startActivity(new Intent(this, MainActivity.class)); + else//否则启动各个类型详情 + UIHelper.showDetail(this, type, id, ""); + break; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + finish(); + } + + @Override + protected int getContentView() { + return R.layout.activity_scheme_url; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/banner/BannerIndicator.java b/app/src/main/java/net/oschina/app/improve/main/banner/BannerIndicator.java new file mode 100644 index 0000000000000000000000000000000000000000..30c2883d5204a19a37b90754ece8702b09aec8fc --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/banner/BannerIndicator.java @@ -0,0 +1,16 @@ +package net.oschina.app.improve.main.banner; + +/** + * Created by haibin + * on 2016/11/24. + */ + +public interface BannerIndicator extends BannerView.OnBannerChangeListener { + void bindBannerView(BannerView view); + + void setCurrentItem(int currentItem); + + void setOnViewChangeListener(BannerView.OnBannerChangeListener listener); + + void notifyDataSetChange(); +} diff --git a/app/src/main/java/net/oschina/app/improve/main/banner/BannerView.java b/app/src/main/java/net/oschina/app/improve/main/banner/BannerView.java new file mode 100644 index 0000000000000000000000000000000000000000..8cad35f7a152169b6389350cdcd465a93d063e4b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/banner/BannerView.java @@ -0,0 +1,547 @@ +package net.oschina.app.improve.main.banner; + +import android.content.Context; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +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.widget.Scroller; + +import net.oschina.app.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * 无限轮播Banner + * Created by haibin + * on 17/2/27. + */ +@SuppressWarnings("all") +public class BannerView extends ViewGroup implements View.OnClickListener { + private int duration; + private BannerTransformer mTransformer; + private float mHorizontalOffset; + private float mVerticalOffset; + private int mTotalScrollX; + private Scroller mScroller; + private ItemInfo mLastViewItem, mPreViewItem, mCurViewItem; + private View mCurrentView; + private DataSetObserver mBannerObserver; + private float mLastX; + private int mTotalDx; + private int mCurItem; + private int mMaximumVelocity; + private float mXVelocity; + private static final int MIN_FLING_VELOCITY = 400; // dips + private BaseBannerAdapter mAdapter; + private VelocityTracker mVelocityTracker; + private boolean isScrollBack; + private boolean isToLeft; + private boolean isTouch; + private List mBannerChangeListeners; + private OnBannerItemClicklistener mOnItemClickListener; + + public BannerView(Context context) { + this(context, null, 0); + } + + public BannerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BannerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setOnClickListener(this); + mScroller = new Scroller(context); + mVelocityTracker = VelocityTracker.obtain(); + final ViewConfiguration configuration = ViewConfiguration.get(context); + final float density = context.getResources().getDisplayMetrics().density; + + mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.BannerView); + mHorizontalOffset = array.getDimension(R.styleable.BannerView_horizontal_offset, 0); + mVerticalOffset = array.getDimension(R.styleable.BannerView_vertical_offset, 0); + duration = array.getInt(R.styleable.BannerView_scroll_duration, 500); + array.recycle(); + + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + isTouch = true; + if (event.getPointerCount() >= 2) return false; + if (mAdapter == null) return super.onTouchEvent(event); + int count = mAdapter.getCount(); + if (count <= 1) return super.onTouchEvent(event); + float x = event.getRawX(); + mVelocityTracker.addMovement(event); + int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + if (!mScroller.isFinished()) { // 如果上次的调用没有执行完就取消。 + return false; + } + mLastX = x; + return true; + case MotionEvent.ACTION_MOVE: + requestDisallowInterceptTouchEvent(true); + int dxMove = (int) (mLastX - x); + mTotalDx += dxMove; + onScrollBy(dxMove); + mLastX = x; + return true; + case MotionEvent.ACTION_UP: + isTouch = false; + if (Math.abs(mTotalDx) <= 10) + performClick(); + case MotionEvent.ACTION_CANCEL: { + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + mXVelocity = velocityTracker.getXVelocity(); + isTouch = false; + scrollToItem(); + scrollFinish(count); + break; + } + } + return super.onTouchEvent(event); + } + + public void setCurrentItem(int item) { + if (item <= 0 && item <= mAdapter.getCount() - 1 && item != mCurItem) { + if (item < mCurItem) mXVelocity = -1301; + else mXVelocity = 1301; + this.mCurItem = item; + scrollToItem(); + scrollFinish(mAdapter.getCount()); + } + } + + @Override + public void onClick(View v) { + if (mOnItemClickListener != null && mAdapter != null && mAdapter.getCount() != 0) + mOnItemClickListener.onItemClick(mCurItem); + } + + public void scrollToNext() { + if (isTouch || mAdapter == null || mAdapter.getCount() <= 1 || !mScroller.isFinished()) + return; + mXVelocity = -1311; + scrollToItem(); + scrollFinish(mAdapter.getCount()); + } + + public void setBannerOnItemClickListener(OnBannerItemClicklistener mOnItemClickListener) { + this.mOnItemClickListener = mOnItemClickListener; + } + + public void setTransformer(BannerTransformer transformer) { + this.mTransformer = transformer; + } + + private void onScrollBy(int dx) { + if (mBannerChangeListeners != null) { + for (OnBannerChangeListener listener : mBannerChangeListeners) { + listener.onViewStateChanged(OnBannerChangeListener.STATE_DRAGGING); + } + } + scrollBy(dx, 0); + isScrollBack = false; + isToLeft = mTotalDx < 0; + if (mTransformer != null) { + mTransformer.onScroll(mCurViewItem.view, mPreViewItem.view, mLastViewItem.view, getWidth(), mTotalDx); + } + if (mBannerChangeListeners != null) { + for (OnBannerChangeListener listener : mBannerChangeListeners) { + listener.onViewScrolled(mCurItem, mTotalDx); + } + } + } + + private void scrollToItem() { + int width = getWidth(); + int dx, scrollX = getScrollX(); + if (Math.abs(mXVelocity) >= 1300) { + if (mXVelocity > 0) {//前一页 + dx = -width - mTotalDx; + scrollX -= 3 * mHorizontalOffset; + dx += 3 * mHorizontalOffset; + isScrollBack = false; + } else {//下一页 + dx = width - mTotalDx; + scrollX += 3 * mHorizontalOffset; + dx -= 3 * mHorizontalOffset; + isScrollBack = false; + } + } else { + if (mTotalDx < 0) { + if (mTotalDx < -(width / 2)) {//前一页 + dx = -width - mTotalDx; + scrollX -= 3 * mHorizontalOffset; + dx += 3 * mHorizontalOffset; + isScrollBack = false; + } else {//右回滚 + isScrollBack = true; + dx = -mTotalDx; + mCurViewItem.view.setScaleY(1); + } + } else { + if (mTotalDx > width / 2) {//下一页 + dx = width - mTotalDx; + scrollX += 3 * mHorizontalOffset; + dx -= 3 * mHorizontalOffset; + isScrollBack = false; + } else {//左回滚 + mCurViewItem.view.setScaleY(1); + dx = -mTotalDx; + isScrollBack = true; + } + } + } + mScroller.startScroll(scrollX, 0, dx, 0, duration); + invalidate(); + } + + /** + * 左边第一个的时候 + */ + private void scrollLeftEdge(int count) { + int width = getWidth(); + View preView = mPreViewItem.view; + int prePosition = mPreViewItem.position; + View curView = mCurViewItem.view; + preView.setScaleY(curView.getScaleY()); + mCurrentView = preView; + int curPosition = mCurViewItem.position; + + int i = (mCurItem + 1) % count; + mAdapter.destroyItem(this, i, mLastViewItem.view);//移除最右边的view + + int index = (mCurItem - 2 + count) % count; + View pre = mAdapter.instantiateItem(index); + mPreViewItem.view = pre; + mPreViewItem.position = index; + addView(pre, 0); + + mTotalScrollX -= width; + + mLastViewItem.position = curPosition; + mLastViewItem.view = curView; + mCurViewItem.view = preView; + mCurViewItem.position = prePosition; + } + + /** + * 左边第一个的时候 + */ + private void scrollRightEdge(int count) { + View lastView = mLastViewItem.view; + int lastPosition = mLastViewItem.position; + View curView = mCurViewItem.view; + mCurrentView = lastView; + lastView.setScaleY(curView.getScaleY()); + int curPosition = mCurViewItem.position; + + int i = (mCurItem - 1) % count; + mAdapter.destroyItem(this, i, mPreViewItem.view);//移除最左边的view + + int index = (mCurItem + 2) % count; + int width = getWidth(); + + View last = mAdapter.instantiateItem(index); + mLastViewItem.view = last; + mLastViewItem.position = index; + addView(last); + + mTotalScrollX += width; + + mCurViewItem.view = lastView; + mCurViewItem.position = lastPosition; + mPreViewItem.view = curView; + mPreViewItem.position = curPosition; + } + + /** + * 滚动完成时 + */ + private void scrollFinish(int count) { + int width = getWidth(); + if (Math.abs(mXVelocity) >= 1300) { + if (mXVelocity > 0) {//前一页 + scrollLeftEdge(count); + if (mCurItem == 0) + mCurItem = count - 1; + else + mCurItem -= 1; + if (mBannerChangeListeners != null) { + for (OnBannerChangeListener listener : mBannerChangeListeners) { + listener.onViewSelected(mCurItem); + } + } + } else {//下一页 + scrollRightEdge(count); + if (mCurItem == count - 1) + mCurItem = 0; + else + mCurItem += 1; + if (mBannerChangeListeners != null) { + for (OnBannerChangeListener listener : mBannerChangeListeners) { + listener.onViewSelected(mCurItem); + } + + } + } + } else { + if (mTotalDx < 0) { + if (mTotalDx <= -(width / 2)) {//前一页 + scrollLeftEdge(count); + if (mCurItem == 0) + mCurItem = count - 1; + else + mCurItem -= 1; + if (mBannerChangeListeners != null) { + for (OnBannerChangeListener listener : mBannerChangeListeners) { + listener.onViewSelected(mCurItem); + } + + } + } + } else { + if (mTotalDx >= width / 2) {//下一页 + scrollRightEdge(count); + if (mCurItem == count - 1) + mCurItem = 0; + else + mCurItem += 1; + if (mBannerChangeListeners != null) { + for (OnBannerChangeListener listener : mBannerChangeListeners) { + listener.onViewSelected(mCurItem); + } + } + } + } + } + + mTotalDx = 0; + } + + @Override + public void computeScroll() { + if (mScroller.computeScrollOffset()) { + if (mTransformer != null) + mTransformer.curPageTransform(mCurrentView, mPreViewItem.view, mLastViewItem.view, isScrollBack, getWidth(), mScroller.getCurrX(), mTotalScrollX, isToLeft); + scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); + invalidate(); + if (mBannerChangeListeners != null) { + for (OnBannerChangeListener listener : mBannerChangeListeners) { + listener.onViewStateChanged(mScroller.getCurrX() % getWidth() == 0 ? OnBannerChangeListener.STATE_IDLE : OnBannerChangeListener.STATE_DRAGGING); + } + } + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View childView = getChildAt(i); + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + int ws, hs; + if (i == 0) { + ws = MeasureSpec.makeMeasureSpec(width - 2 * (int) mHorizontalOffset, MeasureSpec.EXACTLY); + hs = MeasureSpec.makeMeasureSpec(height - 3 * (int) mVerticalOffset, MeasureSpec.EXACTLY); + } else if (i == 1) { + ws = MeasureSpec.makeMeasureSpec(width - 2 * (int) mHorizontalOffset, MeasureSpec.EXACTLY); + hs = MeasureSpec.makeMeasureSpec(height - 2 * (int) mVerticalOffset, MeasureSpec.EXACTLY); + } else { + ws = MeasureSpec.makeMeasureSpec(width - 2 * (int) mHorizontalOffset, MeasureSpec.EXACTLY); + hs = MeasureSpec.makeMeasureSpec(height - 3 * (int) mVerticalOffset, MeasureSpec.EXACTLY); + } + measureChild(childView, ws, hs); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int childCount = getChildCount(); + for (int i = -1; i < childCount - 1; i++) { + View childView = getChildAt(i + 1); + int childW = childView.getMeasuredWidth(); + int childH = childView.getMeasuredHeight(); + int left, right, top, bottom; + if (i == -1) { + if (getChildCount() == 1) { + left = mTotalScrollX + 2 * (int) mHorizontalOffset; + right = childW + mTotalScrollX + (int) mHorizontalOffset; + top = 3 * (int) mVerticalOffset / 2; + bottom = top + childH; + } else { + left = -childW + mTotalScrollX + 2 * (int) mHorizontalOffset; + right = mTotalScrollX + (int) mHorizontalOffset; + top = 3 * (int) mVerticalOffset / 2; + bottom = top + childH; + } + } else if (i == 0) {//当前居中项 + left = mTotalScrollX + (int) mHorizontalOffset; + right = left + childW; + top = (int) mVerticalOffset; + bottom = top + childH; + } else { + left = childW + mTotalScrollX + (int) mHorizontalOffset; + right = left + childW; + top = 3 * (int) mVerticalOffset / 2; + bottom = top + childH; + } + + childView.layout(left, top, right, bottom); + } + } + + public BaseBannerAdapter getAdapter() { + return mAdapter; + } + + public void setAdapter(BaseBannerAdapter adapter) { + if (mAdapter != null) { + mAdapter.setViewPagerObserver(null); + this.mCurItem = 0; + this.removeAllViews(); + this.scrollTo(0, 0); + } + this.mAdapter = adapter; + if (mAdapter != null) { + if (mBannerObserver == null) mBannerObserver = new BannerObserver(); + this.mAdapter.setViewPagerObserver(this.mBannerObserver); + measureView(); + } + } + + public void addOnBannerChangeListener(OnBannerChangeListener mBannerChangeListener) { + if (this.mBannerChangeListeners == null) + this.mBannerChangeListeners = new ArrayList<>(); + this.mBannerChangeListeners.add(mBannerChangeListener); + } + + private void setupViewInfo() { + + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + ViewParent parent = this; + while (parent != null && parent.getParent() != null + && !((parent = parent.getParent()) instanceof ViewPager)) ; + parent.requestDisallowInterceptTouchEvent(true); + return super.dispatchTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return true; + } + + private void measureView() { + int count = mAdapter.getCount(); + if (count == 0) return; + + mCurItem = 0; + View cur = mAdapter.instantiateItem(0); + mCurViewItem = new ItemInfo(); + mCurViewItem.view = cur; + mCurViewItem.position = 0; + mCurrentView = cur; + addView(cur); + + if (count == 1) return; + mPreViewItem = new ItemInfo(); + View pre = mAdapter.instantiateItem(count - 1);//最后一个View + mPreViewItem.view = pre; + mPreViewItem.position = count - 1; + addView(pre, 0); + + mLastViewItem = new ItemInfo(); + View last = mAdapter.instantiateItem(1);//最后一个View + mLastViewItem.view = last; + mLastViewItem.position = 1; + addView(last); + } + + void dataSetChanged() { + mScroller.startScroll(0, 0, 0, 0, 0); + this.mCurItem = 0; + this.removeAllViews(); + mCurViewItem = null; + mPreViewItem = null; + mLastViewItem = null; + mTotalScrollX = 0; + measureView(); + this.scrollTo(0, 0); + requestLayout(); + if (mBannerChangeListeners != null) { + for (OnBannerChangeListener listener : mBannerChangeListeners) { + listener.onViewSelected(mCurItem); + } + } + } + + private class BannerObserver extends DataSetObserver { + BannerObserver() { + super(); + } + + @Override + public void onChanged() { + BannerView.this.dataSetChanged(); + } + + @Override + public void onInvalidated() { + BannerView.this.dataSetChanged(); + } + } + + static class ItemInfo { + View view; + int position; + int pagerWidth; + } + + public interface BannerTransformer { + + void curPageTransform(View cur, View pre, View next, boolean isScrollBack, int parentWidth, int currX, int mTotalScrollX, boolean isToLeft); + + /** + * @param cur 当前 + * @param pre 前一个 + * @param next 下一个 + * @param parentWidth banner的宽 + * @param dx 偏移量 + */ + void onScroll(View cur, View pre, View next, int parentWidth, int dx); + } + + public interface OnBannerChangeListener { + public static final int STATE_IDLE = -1; + public static final int STATE_DRAGGING = 1; + + void onViewScrolled(int position, float positionOffset); + + void onViewSelected(int position); + + void onViewStateChanged(int state); + } + + public interface OnBannerItemClicklistener { + void onItemClick(int position); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/banner/BaseBannerAdapter.java b/app/src/main/java/net/oschina/app/improve/main/banner/BaseBannerAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..f98df8b08965de4799d2964726da08ee1c574046 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/banner/BaseBannerAdapter.java @@ -0,0 +1,55 @@ +package net.oschina.app.improve.main.banner; + +import android.database.DataSetObservable; +import android.database.DataSetObserver; +import android.view.View; + +/** + * Created by haibin + * on 17/2/27. + */ +@SuppressWarnings("all") +public abstract class BaseBannerAdapter { + private final DataSetObservable mObservable = new DataSetObservable(); + private DataSetObserver mViewPagerObserver; + public static final int POSITION_UNCHANGED = -1; + public static final int POSITION_NONE = -2; + + public abstract int getCount(); + + + public abstract View instantiateItem(int position) ; + + public void destroyItem(BannerView container,int position,View item){ + container.removeView(item); + } + + public int getItemPosition(Object object) { + return -1; + } + + public void notifyDataSetChanged() { + synchronized(this) { + if(this.mViewPagerObserver != null) { + this.mViewPagerObserver.onChanged(); + } + } + + this.mObservable.notifyChanged(); + } + + public void registerDataSetObserver(DataSetObserver observer) { + this.mObservable.registerObserver(observer); + } + + public void unregisterDataSetObserver(DataSetObserver observer) { + this.mObservable.unregisterObserver(observer); + } + + void setViewPagerObserver(DataSetObserver observer) { + synchronized(this) { + this.mViewPagerObserver = observer; + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/main/banner/CircleBannerIndicator.java b/app/src/main/java/net/oschina/app/improve/main/banner/CircleBannerIndicator.java new file mode 100644 index 0000000000000000000000000000000000000000..0b539002ca1010ff4cb17eafbf839ddcd5f5d53f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/banner/CircleBannerIndicator.java @@ -0,0 +1,206 @@ +package net.oschina.app.improve.main.banner; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; + +import net.oschina.app.R; + + +/** + * Created by haibin + * on 2016/11/24. + */ +@SuppressWarnings("unused") +public class CircleBannerIndicator extends View implements BannerIndicator { + private float mRadius; + private float mIndicatorRadius; + private final Paint mPaintFill = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint mPaintStroke = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint mPaintIndicator = new Paint(Paint.ANTI_ALIAS_FLAG); + + private int mCurrentPage; + private float mPageOffset; + private boolean mCenterHorizontal; + + private float mIndicatorSpace; + + private BannerView.OnBannerChangeListener mOnViewChangeListener; + private BannerView mBannerView; + + public CircleBannerIndicator(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleBannerIndicator); + + mCenterHorizontal = a.getBoolean(R.styleable.CircleBannerIndicator_circle_banner_indicator_centerHorizontal, true); + mPaintFill.setStyle(Paint.Style.FILL); + mPaintFill.setColor(a.getColor(R.styleable.CircleBannerIndicator_circle_banner_indicator_color, 0x0000ff)); + mPaintStroke.setStyle(Paint.Style.STROKE); + mPaintStroke.setColor(a.getColor(R.styleable.CircleBannerIndicator_circle_banner_indicator_stroke_color, 0x000000)); + mPaintStroke.setStrokeWidth(a.getDimension(R.styleable.CircleBannerIndicator_circle_banner_indicator_stroke_width, 0)); + mPaintIndicator.setStyle(Paint.Style.FILL); + mPaintIndicator.setColor(a.getColor(R.styleable.CircleBannerIndicator_circle_banner_indicator_fill_color, 0x0000ff)); + mRadius = a.getDimension(R.styleable.CircleBannerIndicator_circle_banner_indicator_radius, 10); + mIndicatorSpace = a.getDimension(R.styleable.CircleBannerIndicator_circle_banner_indicator_space, 20); + mIndicatorRadius = a.getDimension(R.styleable.CircleBannerIndicator_circle_banner_indicator_indicator_radius, 10); + if (mIndicatorRadius < mRadius) mIndicatorRadius = mRadius; + a.recycle(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (mBannerView == null) { + return; + } + final int count = mBannerView.getAdapter().getCount(); + + if (count <= 1) { + return; + } + + if (mCurrentPage >= count) { + setCurrentItem(count - 1); + return; + } + + int width = getWidth(); + int paddingLeft = getPaddingLeft(); + int paddingRight = getPaddingRight(); + int paddingTop = getPaddingTop(); + + final float circleAndSpace = 2 * mRadius + mIndicatorSpace;//直径+圆的间隔 + final float yOffset = getHeight() / 2;//竖直方向圆心偏移量,剧中对齐 + float xOffset = paddingLeft + mRadius;//水平方向圆心偏移量 + + //如果采用水平居中对齐 + if (mCenterHorizontal) { + xOffset = (width - count * 2 * mRadius - (count - 1) * mIndicatorSpace) / 2 - mRadius; + } + + float cX; + float cY; + + float strokeRadius = mRadius; + //如果绘制外圆 + if (mPaintStroke.getStrokeWidth() > 0) { + strokeRadius -= mPaintStroke.getStrokeWidth() / 2.0f; + } + + //绘制所有圆点 + for (int i = 0; i < count; i++) { + + cX = xOffset + (i * circleAndSpace);//计算下个圆绘制起点偏移量 + cY = yOffset; + + //绘制圆 + if (mPaintFill.getAlpha() > 0) { + canvas.drawCircle(cX, cY, strokeRadius, mPaintFill); + } + + //绘制外圆 + if (strokeRadius != mRadius) { + canvas.drawCircle(cX, cY, mRadius, mPaintStroke); + } + } + + float cx = mCurrentPage * circleAndSpace; + + cX = xOffset + cx; + cY = yOffset; + canvas.drawCircle(cX, cY, mIndicatorRadius, mPaintIndicator); + } + + @Override + public void bindBannerView(BannerView view) { + if (view == null) + return; + if (view.getAdapter() == null) { + throw new IllegalStateException("BannerView does not set adapter"); + } + this.mBannerView = view; + this.mBannerView.addOnBannerChangeListener(this); + invalidate(); + } + + @Override + public void setCurrentItem(int currentItem) { + if (mBannerView == null) { + throw new IllegalStateException("indicator has not bind BannerView"); + } + //mBannerView.setCurrentItem(currentItem); + mCurrentPage = currentItem; + invalidate(); + } + + @Override + public void setOnViewChangeListener(BannerView.OnBannerChangeListener listener) { + + } + + @Override + public void notifyDataSetChange() { + invalidate(); + requestLayout(); + } + + @Override + public void onViewScrolled(int position, float positionOffset) { + + } + + @Override + public void onViewSelected(int position) { + mCurrentPage = position; + invalidate(); + } + + @Override + public void onViewStateChanged(int state) { + + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); + } + + private int measureWidth(int measureSpec) { + int width; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if ((specMode == MeasureSpec.EXACTLY) || (mBannerView == null)) { + width = specSize; + } else { + final int count = mBannerView.getAdapter().getCount(); + width = (int) (getPaddingLeft() + getPaddingRight() + + (count * 2 * mRadius) + (mIndicatorRadius - mRadius) * 2 + (count - 1) * mIndicatorSpace); + if (specMode == MeasureSpec.AT_MOST) { + width = Math.min(width, specSize); + } + } + return width; + } + + private int measureHeight(int measureSpec) { + int height; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + height = specSize; + } else { + height = (int) (2 * mRadius + getPaddingTop() + getPaddingBottom() + 1); + if (specMode == MeasureSpec.AT_MOST) { + height = Math.min(height, specSize); + } + } + return height; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/banner/EventHeaderView.java b/app/src/main/java/net/oschina/app/improve/main/banner/EventHeaderView.java new file mode 100644 index 0000000000000000000000000000000000000000..7521732ed9f8498d7b9bc3a5ce6acebaa11914a4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/banner/EventHeaderView.java @@ -0,0 +1,52 @@ +package net.oschina.app.improve.main.banner; + +import android.content.Context; +import android.view.View; + +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.bean.Banner; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.widget.ViewEventBanner; + +/** + * Created by haibin + * on 2016/10/26. + */ + +public class EventHeaderView extends HeaderView { + public EventHeaderView(Context context, RequestManager loader, String api, String bannerCache) { + super(context, loader, api, bannerCache); + } + + @Override + protected int getLayoutId() { + return R.layout.layout_event_banner; + } + + @Override + protected void init(Context context) { + super.init(context); + //mBannerView.setTransformer(new ScaleTransform()); + } + + @Override + public void onItemClick(int position) { + Banner banner = mBanners.get(position); + if (banner != null) + EventDetailActivity.show(getContext(), banner.getId()); + } + + @Override + protected View instantiateItem(int position) { + ViewEventBanner view = new ViewEventBanner(getContext()); + if (mBanners.size() != 0) { + int p = position % mBanners.size(); + if (p >= 0 && p < mBanners.size()) { + view.initData(mImageLoader, mBanners.get(p)); + } + } + return view; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/banner/HeaderView.java b/app/src/main/java/net/oschina/app/improve/main/banner/HeaderView.java new file mode 100644 index 0000000000000000000000000000000000000000..96eecb9d2c5de2656b4a2870c4bf5db8328f5b9a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/banner/HeaderView.java @@ -0,0 +1,174 @@ +package net.oschina.app.improve.main.banner; + +import android.content.Context; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.RelativeLayout; + +import com.bumptech.glide.RequestManager; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.bean.Banner; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.utils.CacheManager; + +import java.util.ArrayList; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2016/10/26. + */ + +public abstract class HeaderView extends RelativeLayout implements BannerView.OnBannerChangeListener, + Runnable,BannerView.OnBannerItemClicklistener { + protected BannerView mBannerView; + protected CircleBannerIndicator mIndicator; + protected List mBanners; + protected BannerAdapter mAdapter; + protected Handler mHandler; + protected int mCurrentItem; + protected RequestManager mImageLoader; + protected TextHttpResponseHandler mCallBack; + protected String mUrl; + private boolean isScrolling; + protected String mBannerCache; + + public HeaderView(Context context, RequestManager loader, String api, String bannerCache) { + super(context); + mImageLoader = loader; + this.mUrl = api; + this.mBannerCache = bannerCache; + init(context); + } + + protected void init(Context context) { + mBanners = new ArrayList<>(); + List banners = CacheManager.readListJson(context, mBannerCache, Banner.class); + if (banners != null) { + mBanners.addAll(banners); + if (mHandler == null) + mHandler = new Handler(); + mHandler.postDelayed(this, 5000); + } + LayoutInflater.from(context).inflate(getLayoutId(), this, true); + mBannerView = (BannerView) findViewById(R.id.bannerView); + mBannerView.setBannerOnItemClickListener(this); + mIndicator = (CircleBannerIndicator) findViewById(R.id.indicator); + mAdapter = new BannerAdapter(); + mBannerView.addOnBannerChangeListener(this); + mBannerView.setAdapter(mAdapter); + mIndicator.bindBannerView(mBannerView); + mCallBack = new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + final ResultBean> result = AppOperator.createGson().fromJson(responseString, + new TypeToken>>() { + }.getType()); + if (result != null && result.isSuccess()) { + CacheManager.saveToJson(getContext(), mBannerCache, result.getResult().getItems()); + setBanners(result.getResult().getItems()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }; + requestBanner(); + } + + @Override + public void run() { + mHandler.postDelayed(this, 5000); + if (isScrolling) + return; + mCurrentItem = mCurrentItem + 1; + mBannerView.scrollToNext(); + // mBannerView.setCurrentItem(mCurrentItem); + } + + public void requestBanner() { + if (mHandler == null) + mHandler = new Handler(); + mHandler.removeCallbacks(this); + OSChinaApi.getBanner(mUrl, mCallBack); + } + + void setBanners(List banners) { + if (banners != null) { + mHandler.removeCallbacks(this); + mBanners.clear(); + mBanners.addAll(banners); + mBannerView.getAdapter().notifyDataSetChanged(); + //mIndicator.setCount(mBanners.size()); + mIndicator.notifyDataSetChange(); + if (mBanners.size() > 1) { + mHandler.postDelayed(this, 5000); + } + } + } + + protected abstract int getLayoutId(); + + protected abstract View instantiateItem(int position); + + @Override + public void onViewScrolled(int position, float positionOffset) { + isScrolling = mCurrentItem != position; + } + + @Override + public void onViewSelected(int position) { + isScrolling = false; + mCurrentItem = position; + } + + @Override + public void onViewStateChanged(int state) { + isScrolling = state != BannerView.OnBannerChangeListener.STATE_IDLE; + } + + private class BannerAdapter extends BaseBannerAdapter { + + @Override + public View instantiateItem(int position) { + return HeaderView.this.instantiateItem(position); + } + + @Override + public int getCount() { + return mBanners.size(); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mHandler == null) + mHandler = new Handler(); + mHandler.postDelayed(this, 5000); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mHandler == null) + return; + mHandler.removeCallbacks(this); + mHandler = null; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/banner/NewsHeaderView.java b/app/src/main/java/net/oschina/app/improve/main/banner/NewsHeaderView.java new file mode 100644 index 0000000000000000000000000000000000000000..8efe2fa243126db6a4e5b8eff6e8909ca058509e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/banner/NewsHeaderView.java @@ -0,0 +1,85 @@ +package net.oschina.app.improve.main.banner; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.View; +import android.widget.TextView; + +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.bean.Banner; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.main.synthesize.web.WebActivity; +import net.oschina.app.improve.widget.ViewNewsBanner; +import net.oschina.app.util.UIHelper; + +import java.util.List; + +/** + * Created by haibin + * on 2016/10/26. + */ + +@SuppressLint("ViewConstructor") +public class NewsHeaderView extends HeaderView { + private TextView mTitleTextView; + + public NewsHeaderView(Context context, RequestManager loader, String api, String bannerCache) { + super(context, loader, api, bannerCache); + } + + @Override + protected void init(Context context) { + super.init(context); + //mBannerView.setTransformer(new ScaleTransform()); + mTitleTextView = (TextView) findViewById(R.id.tv_title); + } + + @Override + protected int getLayoutId() { + return R.layout.layout_news_banner; + } + + @Override + public void onViewSelected(int position) { + super.onViewSelected(position); + if (mBanners.size() != 0) + mTitleTextView.setText(mBanners.get(position % mBanners.size()).getName()); + } + + @Override + void setBanners(List banners) { + super.setBanners(banners); + if (banners.size() > 0 && mCurrentItem == 0) { + mTitleTextView.setText(banners.get(0).getName()); + } + } + + @Override + public void onItemClick(int position) { + Banner banner = mBanners.get(position); + if (banner != null) { + int type = banner.getType(); + long id = banner.getId(); + if(type == News.TYPE_HREF){ + //UIHelper.openExternalBrowser(getContext(),banner.getHref()); + WebActivity.show(getContext(),banner.getHref()); + }else { + UIHelper.showDetail(getContext(), type, id, banner.getHref()); + } + } + } + + @Override + protected View instantiateItem(int position) { + ViewNewsBanner view = new ViewNewsBanner(getContext()); + if (mBanners.size() != 0) { + int p = position % mBanners.size(); + if (p >= 0 && p < mBanners.size()) { + view.initData(mImageLoader, mBanners.get(p)); + } + } + return view; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/banner/ScaleTransform.java b/app/src/main/java/net/oschina/app/improve/main/banner/ScaleTransform.java new file mode 100644 index 0000000000000000000000000000000000000000..339025279d9e4a70a8bde49e6de1f3e46330c6ef --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/banner/ScaleTransform.java @@ -0,0 +1,72 @@ +package net.oschina.app.improve.main.banner; + +import android.view.View; + +/** + * Created by haibin + * on 17/3/1. + */ + +public class ScaleTransform implements BannerView.BannerTransformer { + @Override + public void curPageTransform(final View cur, final View pre, final View next, boolean isScrollBack, int parentWidth, int currX, int mTotalScrollX, boolean isToLeft) { + int width = parentWidth / 2; + + int curHeight = cur.getHeight(); + int preHeight = pre.getHeight(); + + int absX = Math.abs((mTotalScrollX - currX) % width); + int diff = curHeight - preHeight; + float add = Math.abs((float) (width - absX) * (float) diff / width); + float curSy = (preHeight + add) / (float) curHeight; + + if (isScrollBack) { + cur.setScaleY(curSy); + } else { + absX = parentWidth - Math.abs((mTotalScrollX - currX)); + add = Math.abs((float) absX * (float) diff / parentWidth); + curSy = (preHeight + add) / (float) curHeight; + cur.setScaleY(curSy); + if (isToLeft) {// -1{ + next.setScaleY(1); + } else { + pre.setScaleY(1); + } + } + } + + @Override + public void onScroll(View cur, View pre, View next, int parentWidth, int dx) { + int width = parentWidth / 2; + + int curHeight = cur.getHeight(); + int preHeight = pre.getHeight(); + + float totalSy = (float) preHeight / curHeight; + int diff = curHeight - preHeight; + float add = Math.abs((float) dx * (float) diff / width); + float curSy = (curHeight - add) / (float) curHeight; + if (dx < 0) {//右滑-1 + if (Math.abs(dx) <= width) { + if (curSy < totalSy) + curSy = totalSy; + cur.setScaleY(curSy); + } else { + add = Math.abs((float) (Math.abs(dx) - width) * (float) diff / width); + curSy = (preHeight + add) / (float) preHeight; + pre.setScaleY(curSy); + } + } + if (dx > 0) {//左滑+1 + if (Math.abs(dx) <= width) { + if (curSy < totalSy) + curSy = totalSy; + cur.setScaleY(curSy); + } else { + add = Math.abs((float) (Math.abs(dx) - width) * (float) diff / width); + curSy = (preHeight + add) / (float) preHeight; + next.setScaleY(curSy); + } + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/discover/BaseSensorFragment.java b/app/src/main/java/net/oschina/app/improve/main/discover/BaseSensorFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..9183dbbe2400955f861386701526f95e92a2fe1a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/discover/BaseSensorFragment.java @@ -0,0 +1,228 @@ +package net.oschina.app.improve.main.discover; + +import android.app.Service; +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.media.MediaPlayer; +import android.os.Handler; +import android.os.Vibrator; +import android.support.v7.widget.CardView; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.ScaleAnimation; +import android.widget.TextView; + +import com.google.gson.GsonBuilder; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.common.widget.Loading; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * 摇一摇基本逻辑实现 + */ +public abstract class BaseSensorFragment extends BaseFragment implements SensorEventListener, View.OnClickListener { + public static final int UPTATE_INTERVAL_TIME = 50; + + protected SensorManager mSensor = null; + protected Vibrator mVibrator = null; + protected int mSpeedThreshold = 45;// 这个值越大需要越大的力气来摇晃手机 + + private float mSensorLastX; + private float mSensorLastY; + private float mSensorLastZ; + private long mSensorLastUpdateTime; + + protected boolean mLoading; + protected boolean mIsRegister; + protected int mDelayTime = 5; + + protected TextHttpResponseHandler mHandler; + protected ResultBean mBean; + protected View mShakeView; + + @Bind(R.id.cv_shake) + CardView mCardView; + + @Bind(R.id.tv_state) + TextView mTvState; + + @Bind(R.id.loading) + Loading mLoadingView; + + @Bind(R.id.tv_time) + TextView mTxtTime; + + protected Handler mTimeHandler; + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mSensor = (SensorManager) getActivity() + .getSystemService(Context.SENSOR_SERVICE); + mVibrator = (Vibrator) getActivity().getSystemService(Service.VIBRATOR_SERVICE); + mCardView.setOnClickListener(this); + } + + @Override + protected void initData() { + super.initData(); + mHandler = new TextHttpResponseHandler() { + @Override + public void onStart() { + super.onStart(); + BaseSensorFragment.this.onRequestStart(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (mContext != null) { + BaseSensorFragment.this.onFailure(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (mContext == null) + return; + try { + mBean = new GsonBuilder().create().fromJson(responseString, getType()); + if (mBean != null && mBean.isSuccess()) { + BaseSensorFragment.this.onSuccess(); + } else { + onFailure(statusCode, headers, responseString, new Exception()); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + + @Override + public void onFinish() { + super.onFinish(); + onTimeProgress(); + } + }; + } + + @Override + public void onClick(View v) { + + } + + @Override + public void onDetach() { + super.onDetach(); + mTimeHandler = null; + } + + public void onShake() { + + } + + + protected void initShakeView() { + + } + + protected void onTimeProgress() { + + } + + protected void onRequestStart() { + mTvState.setVisibility(View.VISIBLE); + mTvState.setText("正在搜寻礼品"); + mLoadingView.setVisibility(View.VISIBLE); + mLoadingView.start(); + } + + protected void onSuccess() { + mCardView.removeAllViews(); + MediaPlayer.create(mContext, R.raw.shake).start(); + initShakeView(); + mCardView.addView(mShakeView); + mCardView.setVisibility(View.VISIBLE); + ScaleAnimation animation = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, + Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); + animation.setDuration(320); + animation.setFillAfter(true); + mCardView.startAnimation(animation); + + mLoadingView.stop(); + mLoadingView.setVisibility(View.GONE); + mTvState.setVisibility(View.GONE); + } + + + protected void onFailure() { + mTvState.setText("很遗憾,你没有摇到礼品,请再试一次"); + } + + @Override + public void onSensorChanged(SensorEvent event) { + long currentUpdateTime = System.currentTimeMillis(); + long timeInterval = currentUpdateTime - mSensorLastUpdateTime; + if (timeInterval < UPTATE_INTERVAL_TIME) { + return; + } + mSensorLastUpdateTime = currentUpdateTime; + + float x = event.values[0]; + float y = event.values[1]; + float z = event.values[2]; + + float deltaX = x - mSensorLastX; + float deltaY = y - mSensorLastY; + float deltaZ = z - mSensorLastZ; + + mSensorLastX = x; + mSensorLastY = y; + mSensorLastZ = z; + + double speed = (Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ + * deltaZ) / timeInterval) * 100; + if (speed >= mSpeedThreshold && !mLoading) { + mLoading = true; + mVibrator.vibrate(300); + onShake(); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + + } + + public void registerSensor() { + if (mSensor != null && !mIsRegister) { + Sensor sensor = mSensor.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + if (sensor != null) { + mIsRegister = true; + mSensor.registerListener(this, sensor, + SensorManager.SENSOR_DELAY_GAME); + } + } + } + + public void unregisterSensor() { + if (mSensor != null && mIsRegister) { + mIsRegister = false; + mSensor.unregisterListener(this); + } + } + + protected Type getType() { + return null; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/discover/QRCodeActivity.java b/app/src/main/java/net/oschina/app/improve/main/discover/QRCodeActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..4763cf4bbed1a8555797fbb53365d1679663a2a9 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/discover/QRCodeActivity.java @@ -0,0 +1,15 @@ +package net.oschina.app.improve.main.discover; + +import net.oschina.app.improve.base.activities.BaseActivity; + +/** + * Created by haibin + * on 2016/11/4. + */ + +public class QRCodeActivity extends BaseActivity { + @Override + protected int getContentView() { + return 0; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/discover/ShakeNewsFragment.java b/app/src/main/java/net/oschina/app/improve/main/discover/ShakeNewsFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..7b4a936f5ded6d727a61cf2ea1c28059d8e86931 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/discover/ShakeNewsFragment.java @@ -0,0 +1,134 @@ +package net.oschina.app.improve.main.discover; + +import android.os.Handler; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.bean.Banner; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.shake.ShakeNews; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; + +import java.lang.reflect.Type; + +/** + * 摇一摇新闻咨询等相关界面实现 + */ +public class ShakeNewsFragment extends BaseSensorFragment { + + private ImageView mImgNews; + private TextView mTxtNewsName, mTxtPubTime; + + public static ShakeNewsFragment newInstance() { + ShakeNewsFragment fragment = new ShakeNewsFragment(); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_shake_news; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mShakeView = mInflater.inflate(R.layout.view_news, null); + mImgNews = (ImageView) mShakeView.findViewById(R.id.iv_news); + mTxtNewsName = (TextView) mShakeView.findViewById(R.id.tv_news_name); + mTxtPubTime = (TextView) mShakeView.findViewById(R.id.tv_time); + mDelayTime = 1; + mCardView.setVisibility(View.GONE); + mTvState.setText("摇一摇获取资讯"); + } + + @Override + public void onClick(View v) { + if (mBean != null) { + Banner banner = new Banner(); + ShakeNews news = mBean.getResult(); + banner.setId(news.getId()); + banner.setType(news.getType()); + banner.setName(news.getName()); + UIHelper.showBannerDetail(mContext, banner); + } + } + + @Override + public void onShake() { + if (!TDevice.hasInternet()) { + Toast.makeText(mContext, "网络连接失败", Toast.LENGTH_SHORT).show(); + mLoading = false; + return; + } + OSChinaApi.getShakeNews(mHandler); + } + + @Override + protected void initShakeView() { + ShakeNews news = mBean.getResult(); + mCardView.setVisibility(View.VISIBLE); + getImgLoader().load(news.getImg()) + .placeholder(R.mipmap.ic_split_graph) + .into(mImgNews); + mTxtNewsName.setText(news.getName()); + mTxtPubTime.setText(StringUtils.formatSomeAgo(news.getPubDate())); + } + + @Override + protected void onRequestStart() { + super.onRequestStart(); + mTvState.setText("正在搜寻资讯"); + } + + @Override + protected void onTimeProgress() { + if (mContext != null) { + if (mTimeHandler == null) + mTimeHandler = new Handler(); + mLoadingView.setVisibility(View.GONE); + //mTxtTime.setVisibility(View.VISIBLE); + //mTxtTime.setText(String.format("%d秒后可再摇一次", mDelayTime)); + mTimeHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (mTxtTime == null) + return; + mTxtTime.setVisibility(View.VISIBLE); + --mDelayTime; + if (mTxtTime == null) + return; + //mTxtTime.setText(String.format("%d秒后可再摇一次", mDelayTime)); + if (mDelayTime > 0) + mTimeHandler.postDelayed(this, 1000); + else { + mTxtTime.setVisibility(View.INVISIBLE); + mTvState.setVisibility(View.VISIBLE); + mTvState.setText("摇一摇获取资讯"); + mLoading = false; + mDelayTime = 1; + } + } + }, 1000); + } + } + + @Override + protected void onFailure() { + super.onFailure(); + mTvState.setText("很遗憾,你没有摇到资讯,请再试一次"); + } + + @Override + protected Type getType() { + return new TypeToken>() { + }.getType(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/discover/ShakePresentActivity.java b/app/src/main/java/net/oschina/app/improve/main/discover/ShakePresentActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..dff5a9652200094f6e1e3a08227ab656d74eff97 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/discover/ShakePresentActivity.java @@ -0,0 +1,101 @@ +package net.oschina.app.improve.main.discover; + +import android.content.Context; +import android.content.Intent; +import android.view.View; +import android.widget.LinearLayout; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 摇一摇活动界面 + */ +public class ShakePresentActivity extends BackActivity implements View.OnClickListener { + + @Bind(R.id.ll_shake_present) + LinearLayout mLayShakePresent; + + @Bind(R.id.ll_shake_news) + LinearLayout mLayShakeNews; + + private ShakePresentFragment mPresentFragment; + private ShakeNewsFragment mNewsFragment; + + private boolean mIsRegisterPresent; + + public static void show(Context context) { + context.startActivity(new Intent(context, ShakePresentActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_shake_present; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mNewsFragment = ShakeNewsFragment.newInstance(); + mPresentFragment = ShakePresentFragment.newInstance(); + addFragment(R.id.fl_content, mNewsFragment); + addFragment(R.id.fl_content, mPresentFragment); + setState(mLayShakePresent, true); + mIsRegisterPresent = true; + } + + @OnClick({R.id.ll_shake_present, R.id.ll_shake_news}) + @Override + public void onClick(View v) { + if (v.getId() == R.id.ll_shake_present) { + addFragment(R.id.fl_content, mPresentFragment); + mNewsFragment.unregisterSensor(); + mPresentFragment.registerSensor(); + setState(mLayShakeNews, false); + setState(mLayShakePresent, true); + mIsRegisterPresent = true; + } else if (v.getId() == R.id.ll_shake_news) { + addFragment(R.id.fl_content, mNewsFragment); + mPresentFragment.unregisterSensor(); + mNewsFragment.registerSensor(); + setState(mLayShakePresent, false); + setState(mLayShakeNews, true); + mIsRegisterPresent = false; + } + } + + @Override + protected void onPause() { + super.onPause(); + mPresentFragment.unregisterSensor(); + mNewsFragment.unregisterSensor(); + } + + @Override + protected void onResume() { + super.onResume(); + if (mIsRegisterPresent) { + mPresentFragment.registerSensor(); + } else { + mNewsFragment.registerSensor(); + } + } + + @Override + public void finish() { + super.finish(); + mPresentFragment.unregisterSensor(); + mNewsFragment.unregisterSensor(); + } + + private void setState(LinearLayout layout, boolean selected) { + for (int i = 0; i < layout.getChildCount(); i++) { + layout.getChildAt(i).setSelected(selected); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/discover/ShakePresentFragment.java b/app/src/main/java/net/oschina/app/improve/main/discover/ShakePresentFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..e3b3e4b2c1706f2f94d121607888d282b795cbad --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/discover/ShakePresentFragment.java @@ -0,0 +1,170 @@ +package net.oschina.app.improve.main.discover; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.os.Handler; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.R; +import net.oschina.app.api.APIVerify; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.shake.ShakePresent; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; +import net.oschina.common.verify.Verifier; + +import java.lang.reflect.Type; + +/** + * 摇一摇礼品相关实现 + */ +public class ShakePresentFragment extends BaseSensorFragment { + private Button mBtnShakeAgain; + private ImageView mImgPig; + private TextView mTxtName; + private boolean mCanAgain; + + public static ShakePresentFragment newInstance() { + return new ShakePresentFragment(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == AppCompatActivity.RESULT_OK && requestCode == 1) { + mLoading = false; + } + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_shake_present; + } + + @SuppressLint("InflateParams") + @Override + protected void initWidget(View root) { + super.initWidget(root); + mShakeView = mInflater.inflate(R.layout.view_present, null); + mBtnShakeAgain = (Button) mShakeView.findViewById(R.id.btn_shake_again); + Button mBtnGet = (Button) mShakeView.findViewById(R.id.btn_get); + mImgPig = (ImageView) mShakeView.findViewById(R.id.iv_pig); + mTxtName = (TextView) mShakeView.findViewById(R.id.tv_name); + mBtnShakeAgain.setOnClickListener(this); + mBtnGet.setOnClickListener(this); + mSpeedThreshold = 70; + mCardView.setVisibility(View.GONE); + mTvState.setText("摇一摇抢礼品"); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_shake_again: + if (!mLoading && mCanAgain) { + mCardView.clearAnimation(); + mCardView.setVisibility(View.GONE); + mCardView.removeAllViews(); + mCanAgain = false; + mLoading = false; + } + break; + case R.id.btn_get: + if (mBean != null && mBean.getResult() != null) { + UIHelper.showUrlRedirect(mContext, mBean.getResult().getHref()); + } + break; + default: + + break; + } + } + + @Override + public void onShake() { + if (!TDevice.hasInternet()) { + Toast.makeText(mContext, "网络连接失败", Toast.LENGTH_SHORT).show(); + mLoading = false; + return; + } + if (!AccountHelper.isLogin()) { + LoginActivity.show(ShakePresentFragment.this, 1); + Toast.makeText(mContext, "摇礼品需要登陆", Toast.LENGTH_LONG).show(); + return; + } + mCanAgain = false; + String appToken = APIVerify.getVerifyString(); + long time = System.currentTimeMillis(); + String sign = Verifier.signStringArray(String.valueOf(time), String.valueOf(AccountHelper.getUserId()), + appToken); + OSChinaApi.getShakePresent(time, appToken, sign, mHandler); + } + + @Override + protected void onTimeProgress() { + if (mContext != null) { + if (mTimeHandler == null) + mTimeHandler = new Handler(); + mLoadingView.setVisibility(View.GONE); + if (mBean != null && mBean.getCode() == 251) {//活动进行中,没摇到 + mBtnShakeAgain.setTextColor(0xFFD8D8D8); + mTxtTime.setVisibility((mBean == null || mBean.getResult() == null) ? View.VISIBLE : View.INVISIBLE); + mTxtTime.setText(String.format("%s秒后可再摇一次", mDelayTime)); + mTimeHandler.postDelayed(new Runnable() { + @Override + public void run() { + --mDelayTime; + if (mTxtTime == null) + return; + if (mBean == null || mBean.getResult() == null) { + mTxtTime.setText(String.format("%s秒后可再摇一次", mDelayTime)); + } else { + mBtnShakeAgain.setText(String.format("再摇一次(%s)", mDelayTime)); + } + if (mDelayTime > 0) + mTimeHandler.postDelayed(this, 1000); + else { + mBtnShakeAgain.setText("再摇一次"); + mBtnShakeAgain.setTextColor(0xFF111111); + mTvState.setText("摇一摇抢礼品"); + mCanAgain = true; + mTxtTime.setVisibility(View.INVISIBLE); + mLoading = mBean != null && mBean.getResult() != null; + mDelayTime = 5; + } + } + }, 1000); + } else { + mTvState.setText(mBean != null ? mBean.getMessage() : "没有摇到礼品"); + mLoading = false; + } + } + } + + @Override + protected void initShakeView() { + ShakePresent present = mBean.getResult(); + mCardView.setVisibility(View.VISIBLE); + getImgLoader().load(present.getPic()).placeholder(R.mipmap.ic_split_graph).into(mImgPig); + mTxtName.setText(present.getName()); + mTvState.setText("恭喜您中奖了"); + mLoading = false; + mCanAgain = true; + } + + @Override + protected Type getType() { + return new TypeToken>() { + }.getType(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/header/BlogHeaderView.java b/app/src/main/java/net/oschina/app/improve/main/header/BlogHeaderView.java new file mode 100644 index 0000000000000000000000000000000000000000..7ef464dc26742f51f3193b5ec65417ed9ef0ccde --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/header/BlogHeaderView.java @@ -0,0 +1,105 @@ +package net.oschina.app.improve.main.header; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.bean.Banner; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.SubTab; +import net.oschina.app.improve.detail.SubActivity; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.media.Util; + +/** + * 每日一博Header + * Created by huanghaibin on 2017/10/26. + */ + +@SuppressLint("ViewConstructor") +public class BlogHeaderView extends HeaderView { + public BlogHeaderView(Context context, String api, String cacheName) { + super(context, api, cacheName); + findViewById(R.id.tv_all).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + SubTab tab = new SubTab(); + tab.setName("每日一博"); + tab.setFixed(false); + tab.setHref("https://www.oschina.net/action/apiv2/sub_list?token=1abf09a23a87442184c2f9bf9dc29e35"); + tab.setNeedLogin(false); + tab.setSubtype(1); + tab.setOrder(4); + tab.setToken("1abf09a23a87442184c2f9bf9dc29e35"); + tab.setType(3); + + SubActivity.show(getContext(), tab); + } + }); + } + + @Override + protected int getLayoutId() { + return R.layout.layout_blog_header; + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new BlogBannerAdapter(getContext()); + } + + @Override + public void onItemClick(int position, long itemId) { + Banner banner = mAdapter.getItem(position); + if (banner == null) + return; + if (banner.getType() == Banner.BANNER_TYPE_BLOG) { + BlogDetailActivity.show(getContext(), banner.getId()); + } + } + + private static final class BlogBannerAdapter extends BaseRecyclerAdapter { + private RequestManager mLoader; + + BlogBannerAdapter(Context context) { + super(context, NEITHER); + mLoader = Glide.with(context); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new Holder(mInflater.inflate(R.layout.item_list_blog_banner, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Banner item, int position) { + Holder h = (Holder) holder; + mLoader.load(item.getImg()) + .fitCenter() + .into(h.mImageBanner); + h.mTextName.setText(item.getName()); + int p = Util.dipTopx(mContext, 16); + ((RecyclerView.LayoutParams) h.itemView.getLayoutParams()).setMargins(position == 0 ? p : 0, 0, p, 0); + } + + private static final class Holder extends RecyclerView.ViewHolder { + ImageView mImageBanner; + TextView mTextName; + + Holder(View itemView) { + super(itemView); + itemView.getLayoutParams().width = Util.getScreenWidth(itemView.getContext()) / 5 * 3; + mImageBanner = (ImageView) itemView.findViewById(R.id.iv_banner); + mTextName = (TextView) itemView.findViewById(R.id.tv_name); + } + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/header/HeaderView.java b/app/src/main/java/net/oschina/app/improve/main/header/HeaderView.java new file mode 100644 index 0000000000000000000000000000000000000000..8339c7f0f00a52608cb800a59a1bd33c6f80348c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/header/HeaderView.java @@ -0,0 +1,99 @@ +package net.oschina.app.improve.main.header; + +import android.content.Context; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.widget.LinearLayout; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.bean.Banner; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.media.Util; +import net.oschina.app.improve.utils.CacheManager; + +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * 新版本Header + * Created by huanghaibin on 2017/10/25. + */ + +public abstract class HeaderView extends LinearLayout implements BaseRecyclerAdapter.OnItemClickListener { + private String mAPI; + private String mCacheName; + protected BaseRecyclerAdapter mAdapter; + + public HeaderView(Context context, String api, String cacheName) { + super(context, null); + this.mAPI = api; + this.mCacheName = cacheName; + init(context); + } + + private void init(Context context) { + LayoutInflater.from(context).inflate(getLayoutId(), this, true); + RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); + mRecyclerView.setLayoutManager(getLayoutManager()); + mAdapter = getAdapter(); + mRecyclerView.setAdapter(mAdapter); + mAdapter.setOnItemClickListener(this); + List banners = CacheManager.readListJson(context, mCacheName, Banner.class); + if (banners != null) { + mAdapter.resetItem(banners); + } + requestBanner(); + } + + + public void requestBanner() { + OSChinaApi.getBanner(mAPI, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + final ResultBean> result = AppOperator.createGson().fromJson(responseString, + new TypeToken>>() { + }.getType()); + if (result != null && result.isSuccess()) { + CacheManager.saveToJson(getContext(), mCacheName, result.getResult().getItems()); + setBanners(result.getResult().getItems()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + void setBanners(List banners) { + mAdapter.resetItem(banners); + } + + protected RecyclerView.LayoutManager getLayoutManager() { + return new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false); + } + + protected abstract int getLayoutId(); + + protected abstract BaseRecyclerAdapter getAdapter(); + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + widthMeasureSpec = MeasureSpec.makeMeasureSpec(Util.getScreenWidth(getContext()), MeasureSpec.EXACTLY); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/header/NewsHeaderView.java b/app/src/main/java/net/oschina/app/improve/main/header/NewsHeaderView.java new file mode 100644 index 0000000000000000000000000000000000000000..55cff7a6f250acd1250418ec14a919b1f709197b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/header/NewsHeaderView.java @@ -0,0 +1,88 @@ +package net.oschina.app.improve.main.header; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.bean.Banner; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.media.Util; +import net.oschina.app.util.UIHelper; + +/** + * 新版本新闻界面 + * Created by huanghaibin on 2017/10/25. + */ + +public class NewsHeaderView extends HeaderView { + public NewsHeaderView(Context context, String api, String cacheName) { + super(context, api, cacheName); + } + + @Override + protected int getLayoutId() { + return R.layout.layout_news_header; + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new NewsBannerAdapter(getContext()); + } + + @Override + public void onItemClick(int position, long itemId) { + Banner banner = mAdapter.getItem(position); + if (banner != null) { + int type = banner.getType(); + long id = banner.getId(); + if(type == News.TYPE_HREF){ + UIHelper.openExternalBrowser(getContext(),banner.getHref()); + }else { + UIHelper.showDetail(getContext(), type, id, banner.getHref()); + } + } + } + + private static final class NewsBannerAdapter extends BaseRecyclerAdapter { + private RequestManager mLoader; + + NewsBannerAdapter(Context context) { + super(context, NEITHER); + mLoader = Glide.with(context); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new Holder(mInflater.inflate(R.layout.item_list_news_banner, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Banner item, int position) { + Holder h = (Holder) holder; + mLoader.load(item.getImg()) + .fitCenter() + .into(h.mImageBanner); + h.mTextName.setText(item.getName()); + } + + private static final class Holder extends RecyclerView.ViewHolder { + ImageView mImageBanner; + TextView mTextName; + + Holder(View itemView) { + super(itemView); + itemView.getLayoutParams().width = Util.getScreenWidth(itemView.getContext()) / 5 * 3; + mImageBanner = (ImageView) itemView.findViewById(R.id.iv_banner); + mTextName = (TextView) itemView.findViewById(R.id.tv_name); + } + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/introduce/ArticleIntroduceActivity.java b/app/src/main/java/net/oschina/app/improve/main/introduce/ArticleIntroduceActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..3db25f9d1851768b1459792eaa76b76305531360 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/introduce/ArticleIntroduceActivity.java @@ -0,0 +1,114 @@ +package net.oschina.app.improve.main.introduce; + +import android.content.Context; +import android.content.Intent; +import android.view.View; +import android.view.WindowManager; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.main.update.OSCSharedPreference; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 推荐列表首页适配 + * Created by huanghaibin on 2017/11/27. + */ + +public class ArticleIntroduceActivity extends BaseActivity implements View.OnClickListener { + + @Bind(R.id.tv_english) + TextView mTextEnglish; + @Bind(R.id.tv_next_english) + TextView mTextNextEnglish; + @Bind(R.id.arrow_up_english) + View mViewUpEnglish; + @Bind(R.id.ll_pop_english) + LinearLayout mLinearPopEnglish; + + @Bind(R.id.tv_recommend) + TextView mTextRecommend; + + @Bind(R.id.tv_next) + TextView mTextNext; + + @Bind(R.id.tv_sure) + TextView mTextSure; + + @Bind(R.id.arrow_up) + View mViewUp; + + @Bind(R.id.ll_pop) + LinearLayout mLinearPop; + + @Bind(R.id.ll_tweet_tip) + LinearLayout mLinearTweetTip; + + @Bind(R.id.ll_tweet_arrow) + LinearLayout mLinearTweetArrow; + + @Bind(R.id.tv_tip_tweet) + TextView mTextTweetTip; + + + public static void show(Context context) { + if (!OSCSharedPreference.getInstance().isFirstUsing()) { + return; + } + context.startActivity(new Intent(context, ArticleIntroduceActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_article_introduce; + } + + @Override + protected void initWidget() { + super.initWidget(); + getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); + } + + @OnClick({R.id.tv_sure, R.id.tv_next,R.id.tv_next_english}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.tv_next_english: + mTextNextEnglish.setVisibility(View.GONE); + mTextEnglish.setVisibility(View.GONE); + mLinearPopEnglish.setVisibility(View.GONE); + mViewUpEnglish.setVisibility(View.GONE); + + mTextSure.setVisibility(View.VISIBLE); + mLinearTweetTip.setVisibility(View.VISIBLE); + mTextTweetTip.setVisibility(View.VISIBLE); + mLinearTweetArrow.setVisibility(View.VISIBLE); + break; + case R.id.tv_sure: + OSCSharedPreference.getInstance().putFirstUsing(); + finish(); + break; + case R.id.tv_next: + mTextNext.setVisibility(View.GONE); + mTextRecommend.setVisibility(View.GONE); + mLinearPop.setVisibility(View.GONE); + mViewUp.setVisibility(View.GONE); + + mTextNextEnglish.setVisibility(View.VISIBLE); + mTextEnglish.setVisibility(View.VISIBLE); + mLinearPopEnglish.setVisibility(View.VISIBLE); + mViewUpEnglish.setVisibility(View.VISIBLE); + break; + } + } + + @Override + public void finish() { + super.finish(); + overridePendingTransition(0, 0); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/introduce/BezierLayout.java b/app/src/main/java/net/oschina/app/improve/main/introduce/BezierLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..0aa05f0e012266bd2abaa0b5703fa0fbdeff0b4e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/introduce/BezierLayout.java @@ -0,0 +1,44 @@ +package net.oschina.app.improve.main.introduce; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.util.AttributeSet; +import android.view.View; + +import net.oschina.app.improve.media.Util; + +/** + * 贝塞尔曲线布局 + * Created by huanghaibin on 2017/11/24. + */ + +public class BezierLayout extends View{ + private Path mPath = new Path(); + private Paint mPaint = new Paint(); + public BezierLayout(Context context) { + this(context,null); + } + + public BezierLayout(Context context, AttributeSet attrs) { + super(context, attrs); + mPaint.setColor(0xff24cf5f); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setAntiAlias(true); + mPaint.setStrokeWidth(10); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + mPath.reset(); + mPath.lineTo(0, Util.dipTopx(getContext(),200)); + mPath.quadTo(Util.dipTopx(getContext(),70), + Util.dipTopx(getContext(),400), + getWidth(), + Util.dipTopx(getContext(),160)); + mPath.lineTo(getWidth(),0); + canvas.drawPath(mPath, mPaint); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/introduce/IntroduceActivity.java b/app/src/main/java/net/oschina/app/improve/main/introduce/IntroduceActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..d15cd9f0db0f19b9e6ae47d5374e57020cd74e49 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/introduce/IntroduceActivity.java @@ -0,0 +1,64 @@ +package net.oschina.app.improve.main.introduce; + +import android.content.Context; +import android.content.Intent; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.view.ViewPager; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.main.update.OSCSharedPreference; + +import butterknife.Bind; + +/** + * 介绍页 + * Created by huanghaibin on 2017/11/24. + */ + +public class IntroduceActivity extends BaseActivity { + + @Bind(R.id.viewPager) + ViewPager mViewPager; + + public static void show(Context context) { + context.startActivity(new Intent(context, IntroduceActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_introduce; + } + + @Override + protected void initWidget() { + super.initWidget(); + OSCSharedPreference.getInstance().putFirstInstall(); + setSwipeBackEnable(false); + mViewPager.setAdapter(new FragmentAdapter(getSupportFragmentManager())); + mViewPager.getAdapter().notifyDataSetChanged(); + } + + private class FragmentAdapter extends FragmentStatePagerAdapter { + private FragmentAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + return position == 0 ? OneFragment.newInstance() : TwoFragment.newInstance(); + } + + @Override + public int getCount() { + return 2; + } + } + + @Override + public void onBackPressed() { + //super.onBackPressed(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/introduce/OneFragment.java b/app/src/main/java/net/oschina/app/improve/main/introduce/OneFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..22a1a16e34bd6b218de7bdd5600740c954bd9a18 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/introduce/OneFragment.java @@ -0,0 +1,48 @@ +package net.oschina.app.improve.main.introduce; + +import android.view.View; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + +import net.oschina.app.R; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.main.synthesize.article.RatioLayout; +import net.oschina.app.improve.media.Util; + +import butterknife.Bind; + +/** + * 介绍页1 + * Created by huanghaibin on 2017/11/24. + */ + +public class OneFragment extends BaseFragment { + + @Bind(R.id.ll_logo) + LinearLayout mLinearLogo; + @Bind(R.id.ratioLayout) + RatioLayout mRatioLayout; + + static OneFragment newInstance() { + return new OneFragment(); + } + + + @Override + protected int getLayoutId() { + return R.layout.fragment_one; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mLinearLogo.post(new Runnable() { + @Override + public void run() { + RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mLinearLogo.getLayoutParams(); + params.setMargins(0, (Util.getScreenHeight(mContext) - mRatioLayout.getRatioHeight()) / 4, 0, 0); + mLinearLogo.setLayoutParams(params); + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/introduce/TwoFragment.java b/app/src/main/java/net/oschina/app/improve/main/introduce/TwoFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..e9b345fc5efff510e938986235b9bde3aa40c9bd --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/introduce/TwoFragment.java @@ -0,0 +1,33 @@ +package net.oschina.app.improve.main.introduce; + +import android.view.View; + +import net.oschina.app.R; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.main.MainActivity; + +import butterknife.OnClick; + +/** + * 介绍页2 + * Created by huanghaibin on 2017/11/24. + */ + +public class TwoFragment extends BaseFragment { + + static TwoFragment newInstance() { + return new TwoFragment(); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_two; + } + + @SuppressWarnings("unused") + @OnClick({R.id.btn_introduce}) + public void onClick(View view) { + MainActivity.show(mContext); + getActivity().finish(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/location/BDLocationAdapter.java b/app/src/main/java/net/oschina/app/improve/main/location/BDLocationAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..a6952c0e0e105366807d1fdc8cac1000e1ca3caf --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/location/BDLocationAdapter.java @@ -0,0 +1,18 @@ +package net.oschina.app.improve.main.location; + +import com.baidu.location.BDLocation; +import com.baidu.location.BDLocationListener; + +/** + * Created by jzz + * on 2017/2/8. + * desc: + */ + +public class BDLocationAdapter implements BDLocationListener { + + @Override + public void onReceiveLocation(BDLocation bdLocation) { + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/location/RadarSearchAdapter.java b/app/src/main/java/net/oschina/app/improve/main/location/RadarSearchAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..8633678a39dec7b9d189dd6e495e177fc58d15cf --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/location/RadarSearchAdapter.java @@ -0,0 +1,29 @@ +package net.oschina.app.improve.main.location; + +import com.baidu.mapapi.radar.RadarNearbyResult; +import com.baidu.mapapi.radar.RadarSearchError; +import com.baidu.mapapi.radar.RadarSearchListener; + +/** + * Created by jzz + * on 2017/2/8. + * desc: + */ + +public class RadarSearchAdapter implements RadarSearchListener { + + @Override + public void onGetNearbyInfoList(RadarNearbyResult radarNearbyResult, RadarSearchError radarSearchError) { + + } + + @Override + public void onGetUploadState(RadarSearchError radarSearchError) { + + } + + @Override + public void onGetClearInfoState(RadarSearchError radarSearchError) { + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/nav/NavFragment.java b/app/src/main/java/net/oschina/app/improve/main/nav/NavFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..04404fbd46a4f27e0f6a510c10348ac19b37365e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/nav/NavFragment.java @@ -0,0 +1,237 @@ +package net.oschina.app.improve.main.nav; + + +import android.content.Context; +import android.graphics.RectF; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.view.View; +import android.widget.ImageView; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.main.ExploreFragment; +import net.oschina.app.improve.main.synthesize.SynthesizeFragment; +import net.oschina.app.improve.main.tweet.TweetPagerFragment; +import net.oschina.app.improve.notice.NoticeBean; +import net.oschina.app.improve.notice.NoticeManager; +import net.oschina.app.improve.tweet.activities.TweetPublishActivity; +import net.oschina.app.improve.user.activities.UserFansActivity; +import net.oschina.app.improve.user.activities.UserMessageActivity; +import net.oschina.app.improve.user.fragments.UserInfoFragment; +import net.oschina.common.widget.drawable.shape.BorderShape; + +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; +import butterknife.OnLongClick; + +/** + * A simple {@link Fragment} subclass. + */ +public class NavFragment extends BaseFragment implements View.OnClickListener, NoticeManager.NoticeNotify, View.OnLongClickListener { + @Bind(R.id.nav_item_news) + NavigationButton mNavNews; + @Bind(R.id.nav_item_tweet) + NavigationButton mNavTweet; + @Bind(R.id.nav_item_explore) + NavigationButton mNavExplore; + @Bind(R.id.nav_item_me) + NavigationButton mNavMe; + @Bind(R.id.nav_item_tweet_pub) + ImageView mNavPub; + private Context mContext; + private int mContainerId; + private FragmentManager mFragmentManager; + private NavigationButton mCurrentNavButton; + private OnNavigationReselectListener mOnNavigationReselectListener; + + public NavFragment() { + // Required empty public constructor + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_nav; + } + + @SuppressWarnings("deprecation") + @Override + protected void initWidget(View root) { + super.initWidget(root); + + ShapeDrawable lineDrawable = new ShapeDrawable(new BorderShape(new RectF(0, 1, 0, 0))); + lineDrawable.getPaint().setColor(getResources().getColor(R.color.list_divider_color)); + LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{ + new ColorDrawable(getResources().getColor(R.color.white)), + lineDrawable + }); + root.setBackgroundDrawable(layerDrawable); + + mNavNews.init(R.drawable.tab_icon_new, + R.string.main_tab_name_news, + SynthesizeFragment.class); + + mNavTweet.init(R.drawable.tab_icon_tweet, + R.string.main_tab_name_tweet, + TweetPagerFragment.class); + + mNavExplore.init(R.drawable.tab_icon_explore, + R.string.main_tab_name_explore, + ExploreFragment.class); + + mNavMe.init(R.drawable.tab_icon_me, + R.string.main_tab_name_my, + UserInfoFragment.class); + + + } + + @OnClick({R.id.nav_item_news, R.id.nav_item_tweet, + R.id.nav_item_explore, R.id.nav_item_me, + R.id.nav_item_tweet_pub}) + @Override + public void onClick(View v) { + if (v instanceof NavigationButton) { + NavigationButton nav = (NavigationButton) v; + doSelect(nav); + } else if (v.getId() == R.id.nav_item_tweet_pub) { + PubActivity.show(getContext()); + //TweetPublishActivity.show(getContext(), mRoot.findViewById(R.id.nav_item_tweet_pub)); + } + } + + @OnLongClick({R.id.nav_item_tweet_pub}) + @Override + public boolean onLongClick(View v) { + TweetPublishActivity.show(getContext(), mRoot.findViewById(R.id.nav_item_tweet_pub)); + return false; + } + + public void setup(Context context, FragmentManager fragmentManager, int contentId, OnNavigationReselectListener listener) { + mContext = context; + mFragmentManager = fragmentManager; + mContainerId = contentId; + mOnNavigationReselectListener = listener; + + // do clear + clearOldFragment(); + // do select first + doSelect(mNavNews); + } + + public void select(int index) { + if (mNavMe != null) + doSelect(mNavMe); + } + + @SuppressWarnings("RestrictedApi") + private void clearOldFragment() { + FragmentTransaction transaction = mFragmentManager.beginTransaction(); + List fragments = mFragmentManager.getFragments(); + if (transaction == null || fragments == null || fragments.size() == 0) + return; + boolean doCommit = false; + for (Fragment fragment : fragments) { + if (fragment != this && fragment != null) { + transaction.remove(fragment); + doCommit = true; + } + } + if (doCommit) + transaction.commitNow(); + } + + private void doSelect(NavigationButton newNavButton) { + // If the new navigation is me info fragment, we intercept it + /* + if (newNavButton == mNavMe) { + if (interceptMessageSkip()) + return; + } + */ + + NavigationButton oldNavButton = null; + if (mCurrentNavButton != null) { + oldNavButton = mCurrentNavButton; + if (oldNavButton == newNavButton) { + onReselect(oldNavButton); + return; + } + oldNavButton.setSelected(false); + } + newNavButton.setSelected(true); + doTabChanged(oldNavButton, newNavButton); + mCurrentNavButton = newNavButton; + } + + private void doTabChanged(NavigationButton oldNavButton, NavigationButton newNavButton) { + FragmentTransaction ft = mFragmentManager.beginTransaction(); + if (oldNavButton != null) { + if (oldNavButton.getFragment() != null) { + ft.detach(oldNavButton.getFragment()); + } + } + if (newNavButton != null) { + if (newNavButton.getFragment() == null) { + Fragment fragment = Fragment.instantiate(mContext, + newNavButton.getClx().getName(), null); + ft.add(mContainerId, fragment, newNavButton.getTag()); + newNavButton.setFragment(fragment); + } else { + ft.attach(newNavButton.getFragment()); + } + } + ft.commit(); + } + + /** + * 拦截底部点击,当点击个人按钮时进行消息跳转 + */ + private boolean interceptMessageSkip() { + NoticeBean bean = NoticeManager.getNotice(); + if (bean.getAllCount() > 0) { + if (bean.getLetter() + bean.getMention() + bean.getReview() > 0) + UserMessageActivity.show(getActivity()); + else + UserFansActivity.show(getActivity(), AccountHelper.getUserId()); + return true; + } + return false; + } + + private void onReselect(NavigationButton navigationButton) { + OnNavigationReselectListener listener = mOnNavigationReselectListener; + if (listener != null) { + listener.onReselect(navigationButton); + } + } + + @Override + public void onNoticeArrived(NoticeBean bean) { + mNavMe.showRedDot(bean.getUserCount()); + } + + public interface OnNavigationReselectListener { + void onReselect(NavigationButton navigationButton); + } + + @Override + public void onDestroy() { + super.onDestroy(); + NoticeManager.unBindNotify(this); + } + + @Override + protected void initData() { + super.initData(); + NoticeManager.bindNotify(this); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/nav/NavigationButton.java b/app/src/main/java/net/oschina/app/improve/main/nav/NavigationButton.java new file mode 100644 index 0000000000000000000000000000000000000000..df4a16801f503e5b41796119607050752f903364 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/nav/NavigationButton.java @@ -0,0 +1,94 @@ +package net.oschina.app.improve.main.nav; + +import android.content.Context; +import android.os.Build; +import android.support.annotation.DrawableRes; +import android.support.annotation.RequiresApi; +import android.support.annotation.StringRes; +import android.support.v4.app.Fragment; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import net.oschina.app.R; + +/** + * Created by JuQiu + * on 16/8/18. + */ +public class NavigationButton extends FrameLayout { + private Fragment mFragment = null; + private Class mClx; + private ImageView mIconView; + private TextView mTitleView; + private TextView mDot; + private String mTag; + + public NavigationButton(Context context) { + super(context); + init(); + } + + public NavigationButton(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public NavigationButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public NavigationButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + LayoutInflater inflater = LayoutInflater.from(getContext()); + inflater.inflate(R.layout.layout_nav_item, this, true); + + mIconView = (ImageView) findViewById(R.id.nav_iv_icon); + mTitleView = (TextView) findViewById(R.id.nav_tv_title); + mDot = (TextView) findViewById(R.id.nav_tv_dot); + } + + public void setSelected(boolean selected) { + super.setSelected(selected); + mIconView.setSelected(selected); + mTitleView.setSelected(selected); + } + + public void showRedDot(int count) { + mDot.setVisibility(count > 0 ? VISIBLE : GONE); + mDot.setText(String.valueOf(count)); + } + + public void init(@DrawableRes int resId, @StringRes int strId, Class clx) { + mIconView.setImageResource(resId); + mTitleView.setText(strId); + mClx = clx; + mTag = mClx.getName(); + } + + public Class getClx() { + return mClx; + } + + public Fragment getFragment() { + return mFragment; + } + + public void setFragment(Fragment fragment) { + this.mFragment = fragment; + } + + public String getTag() { + return mTag; + } + + +} diff --git a/app/src/main/java/net/oschina/app/improve/main/nav/PubActivity.java b/app/src/main/java/net/oschina/app/improve/main/nav/PubActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..04d6338aef76e1d1637930a0a14ad307f81148f1 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/nav/PubActivity.java @@ -0,0 +1,182 @@ +package net.oschina.app.improve.main.nav; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.main.synthesize.pub.PubArticleActivity; +import net.oschina.app.improve.media.Util; +import net.oschina.app.improve.tweet.activities.TweetPublishActivity; +import net.oschina.app.improve.write.WriteActivity; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 发布选择界面 + * Created by huanghaibin on 2017/9/25. + */ + +public class PubActivity extends BaseActivity implements View.OnClickListener { + + @Bind(R.id.btn_pub) + ImageView mBtnPub; + + @Bind({R.id.ll_pub_article, R.id.ll_pub_blog, R.id.ll_pub_tweet}) + LinearLayout[] mLays; + + public static void show(Context context) { + context.startActivity(new Intent(context, PubActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_pub; + } + + @Override + protected void initWindow() { + super.initWindow(); + getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); + } + + @Override + protected void initWidget() { + super.initWidget(); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + mBtnPub.animate() + .rotation(135.0f) + .setDuration(180) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + } + }) + .start(); + show(0); + show(1); + show(2); + } + + @OnClick({R.id.rl_main, R.id.ll_pub_tweet, R.id.ll_pub_blog, R.id.ll_pub_article}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.rl_main: + dismiss(); + break; + case R.id.ll_pub_tweet: + if (!AccountHelper.isLogin()) { + LoginActivity.show(this); + finish(); + return; + } + TweetPublishActivity.show(this, mBtnPub); + finish(); + break; + case R.id.ll_pub_blog: + if (!AccountHelper.isLogin()) { + LoginActivity.show(this); + finish(); + return; + } + WriteActivity.show(this); + finish(); + break; + case R.id.ll_pub_article: + if (!AccountHelper.isLogin()) { + LoginActivity.show(this); + finish(); + return; + } + PubArticleActivity.show(this, ""); + finish(); + break; + } + } + + private void dismiss() { + close(); + close(0); + close(1); + close(2); + } + + private void close() { + mBtnPub.clearAnimation(); + mBtnPub.animate() + .rotation(0f) + .setDuration(200) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mBtnPub.setVisibility(View.GONE); + finish(); + } + }) + .start(); + } + + private void show(int position) { + int angle = 30 + position * 60; + + float x = (float) Math.cos(angle * (Math.PI / 180)) * Util.dipTopx(this, 100); + float y = (float) -Math.sin(angle * (Math.PI / 180)) * Util.dipTopx(this, position != 1 ? 160 : 100); + ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(mLays[position], "translationX", 0, x); + ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(mLays[position], "translationY", 0, y); + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.setDuration(180); + animatorSet.play(objectAnimatorX).with(objectAnimatorY); + animatorSet.start(); + } + + private void close(final int position) { + int angle = 30 + position * 60; + float x = (float) Math.cos(angle * (Math.PI / 180)) * Util.dipTopx(this, 100); + float y = (float) -Math.sin(angle * (Math.PI / 180)) * Util.dipTopx(this, position != 1 ? 160 : 100); + ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(mLays[position], "translationX", x, 0); + ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(mLays[position], "translationY", y, 0); + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.setDuration(180); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.play(objectAnimatorX).with(objectAnimatorY); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mLays[position].setVisibility(View.GONE); + } + }); + animatorSet.start(); + } + + @Override + public void onBackPressed() { + dismiss(); + } + + @Override + public void finish() { + super.finish(); + overridePendingTransition(0, 0); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/splash/AdFragment.java b/app/src/main/java/net/oschina/app/improve/main/splash/AdFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..8fc969f188870b935fa166d0b5f33ee92004ac6d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/splash/AdFragment.java @@ -0,0 +1,102 @@ +package net.oschina.app.improve.main.splash; + +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.signature.StringSignature; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.bean.Launcher; +import net.oschina.app.improve.main.MainActivity; +import net.oschina.app.util.UIHelper; + +import java.util.UUID; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 首页启动广告 + * Created by huanghaibin on 2017/11/25. + */ + +public class AdFragment extends BaseFragment implements View.OnClickListener { + + @Bind(R.id.countDownView) + CountDownView mCountDownView; + private Launcher mLauncher; + + @Bind(R.id.iv_ad) + ImageView mImageAd; + + private static boolean isClickAd; + + public static AdFragment newInstance(Launcher launcher) { + + AdFragment fragment = new AdFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable("launcher", launcher); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_ad; + } + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + mLauncher = (Launcher) bundle.getSerializable("launcher"); + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + isClickAd = false; + Glide.with(mContext) + .load(OSCApplication.getInstance().getCacheDir() + "/launcher") + .signature(new StringSignature(UUID.randomUUID().toString())) + .fitCenter() + .into(mImageAd); + mCountDownView.setListener(new CountDownView.OnProgressListener() { + @Override + public void onFinish() { + if (isClickAd || mContext == null) + return; + MainActivity.show(mContext); + mCountDownView.cancel(); + getActivity().finish(); + } + }); + mCountDownView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (isClickAd || mContext == null) + return; + mCountDownView.cancel(); + MainActivity.show(mContext); + getActivity().finish(); + } + }); + mCountDownView.start(); + } + + @OnClick({R.id.iv_ad, R.id.iv_logo}) + @Override + public void onClick(View v) { + if (TextUtils.isEmpty(mLauncher.getHref())) { + return; + } + isClickAd = true; + mCountDownView.cancel(); + UIHelper.showUrlRedirect(mContext, mLauncher.getHref()); + getActivity().finish(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/splash/CountDownView.java b/app/src/main/java/net/oschina/app/improve/main/splash/CountDownView.java new file mode 100644 index 0000000000000000000000000000000000000000..1e7b60cea6127ebba08accb8bb3046d2bb8edb96 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/splash/CountDownView.java @@ -0,0 +1,139 @@ +package net.oschina.app.improve.main.splash; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.view.animation.LinearInterpolator; + +import net.oschina.app.improve.media.Util; + +/** + * 倒计时View + * Created by huanghaibin on 2017/11/25. + */ + +public class CountDownView extends View implements Runnable { + private Paint mPaint = new Paint(); + private Paint mTextPaint = new Paint(); + private Paint mCirclePaint = new Paint(); + private int mProgress; + private boolean isCancel; + private RectF mOVal; + private int mCount = 5; + private OnProgressListener mListener; + protected float mTextBaseLine; + + public CountDownView(Context context) { + this(context, null); + } + + public CountDownView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mPaint.setAntiAlias(true); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeWidth(Util.dipTopx(context, 2)); + mPaint.setColor(0xff24cf5f); + + mCirclePaint.setAntiAlias(true); + mCirclePaint.setStyle(Paint.Style.FILL); + mCirclePaint.setColor(0xFFFFFFFF); + + mTextPaint.setAntiAlias(true); + mTextPaint.setStyle(Paint.Style.STROKE); + mTextPaint.setColor(0xFF333333); + mTextPaint.setTextSize(Util.dipTopx(context, 12)); + + Paint.FontMetrics metrics = mTextPaint.getFontMetrics(); + mTextBaseLine = metrics.descent + (metrics.bottom - metrics.top) / 2; + + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mOVal == null) { + mOVal = new RectF(); + mOVal.left = getPaddingLeft(); + mOVal.top = getPaddingTop(); + mOVal.right = getWidth() - getPaddingRight(); + mOVal.bottom = getHeight() - getPaddingBottom(); + } + int w = getWidth(); + int h = getHeight(); + int radius = (w - 2 * getPaddingLeft()) / 2 - Util.dipTopx(getContext(), 1); + int mStartAngle = -90; + canvas.drawArc(mOVal, mStartAngle, mProgress, true, mPaint); + canvas.drawCircle(w / 2, h / 2, radius, mCirclePaint); + canvas.drawText("跳过", w / 2 - getTextX(), h / 3 + mTextBaseLine, mTextPaint); + } + + @Override + public void run() { + if (isCancel) { + mProgress = 0; + return; + } + mProgress += 2; + if (mProgress < 360) { + postDelayed(this, 1000 * mCount / 180); + invalidate(); + } else { + if (mListener != null) { + mListener.onFinish(); + } + } + } + + public void start() { + //postDelayed(this, 1000 * mCount / 180); + ValueAnimator animator = ValueAnimator.ofInt(0, 360); + animator.setDuration(5000); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (isCancel) { + return; + } + mProgress = (int)animation.getAnimatedValue(); + invalidate(); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (mListener != null) { + mListener.onFinish(); + } + } + }); + animator.start(); + } + + public void cancel() { + isCancel = true; + } + + public void setListener(OnProgressListener mListener) { + this.mListener = mListener; + } + + private float getTextX() { + Rect bounds = new Rect(); + mTextPaint.getTextBounds("跳过", 0, "跳过".length(), bounds); + return bounds.width() / 2; + } + + public interface OnProgressListener { + void onFinish(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/splash/SplashActivity.java b/app/src/main/java/net/oschina/app/improve/main/splash/SplashActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..2662a94b1070c7d815b302a3e9d36089c7f3c3da --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/splash/SplashActivity.java @@ -0,0 +1,126 @@ +package net.oschina.app.improve.main.splash; + +import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; +import android.view.View; +import android.widget.FrameLayout; + +import com.baidu.mobstat.SendStrategyEnum; +import com.baidu.mobstat.StatService; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.Setting; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.bean.Launcher; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.main.MainActivity; +import net.oschina.app.improve.main.introduce.IntroduceActivity; +import net.oschina.app.improve.main.tabs.DynamicTabFragment; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.improve.utils.CacheManager; + +import java.io.File; + +import butterknife.Bind; + +/** + * 启动页设计 + * Created by huanghaibin on 2017/11/25. + */ + +public class SplashActivity extends BaseActivity { + + + @Bind(R.id.frameSplash) + FrameLayout mFlameSplash; + + @Bind(R.id.fl_content) + FrameLayout mFlameContent; + + private boolean isShowAd; + + public static void show(Context context) { + context.startActivity(new Intent(context, SplashActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_splash; + } + + @Override + protected void initWidget() { + super.initWidget(); + } + + + @Override + protected void initData() { + super.initData(); + StatService.setSendLogStrategy(this, SendStrategyEnum.APP_START, 1, false); + StatService.start(this); + Launcher launcher = CacheManager.readJson(OSCApplication.getInstance(), "Launcher", Launcher.class); + String savePath = OSCApplication.getInstance().getCacheDir() + "/launcher"; + File file = new File(savePath); + if (launcher != null && !launcher.isExpired() && file.exists()) { + isShowAd = true; + mFlameSplash.setVisibility(View.GONE); + mFlameContent.setVisibility(View.VISIBLE); + addFragment(R.id.fl_content, AdFragment.newInstance(launcher)); + } + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + doMerge(); + } + }); + } + + private void doMerge() { + // 判断是否是新版本 + if (Setting.checkIsNewVersion(this)) { + // Cookie迁移 + String cookie = OSCApplication.getInstance().getProperty("cookie"); + if (!TextUtils.isEmpty(cookie)) { + OSCApplication.getInstance().removeProperty("cookie"); + User user = AccountHelper.getUser(); + user.setCookie(cookie); + AccountHelper.updateUserCache(user); + OSCApplication.reInit(); + } + } + + // 栏目相关数据合并操作 + DynamicTabFragment.initTabPickerManager(); + + if (isShowAd) + return; + // Delay... + try { + Thread.sleep(800); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // 完成后进行跳转操作 + redirectTo(); + } + + private void redirectTo() { + if (OSCSharedPreference.getInstance().isFirstInstall()) { + IntroduceActivity.show(this); + } else { + MainActivity.show(this); + } + finish(); + } + + @Override + public void onBackPressed() { + //super.onBackPressed(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/sub/SubContract.java b/app/src/main/java/net/oschina/app/improve/main/sub/SubContract.java new file mode 100644 index 0000000000000000000000000000000000000000..92a2d124366ef4c5f024f9e4a4f76c2fcf650991 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/sub/SubContract.java @@ -0,0 +1,23 @@ +package net.oschina.app.improve.main.sub; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.bean.SubBean; + +/** + * 订阅接口 + * Created by huanghaibin on 2017/12/18. + */ + +public interface SubContract { + + interface View extends BaseListView { + void onUpdateTime(String time); + + void updateKey(); + } + + interface Presenter extends BaseListPresenter { + void loadCache(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/sub/SubFragment.java b/app/src/main/java/net/oschina/app/improve/main/sub/SubFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..18cd857ebbea1b5a3ad34fb58e9424ba059056b1 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/sub/SubFragment.java @@ -0,0 +1,204 @@ +package net.oschina.app.improve.main.sub; + +import android.os.Bundle; +import android.view.View; + +import net.oschina.app.AppConfig; +import net.oschina.app.OSCApplication; +import net.oschina.app.api.ApiHttpClient; +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.SubTab; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.main.banner.EventHeaderView; +import net.oschina.app.improve.main.header.BlogHeaderView; +import net.oschina.app.improve.main.header.HeaderView; +import net.oschina.app.improve.main.subscription.BlogSubAdapter; +import net.oschina.app.improve.main.subscription.EventSubAdapter; +import net.oschina.app.improve.main.subscription.NewsSubAdapter; +import net.oschina.app.improve.main.subscription.QuestionSubAdapter; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.interf.OnTabReselectListener; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; + +/** + * 订阅栏目 + * Created by huanghaibin on 2017/12/18. + */ + +public class SubFragment extends BaseRecyclerFragment implements SubContract.View ,OnTabReselectListener{ + private SubTab mTab; + private HeaderView mHeaderView; + private BlogHeaderView mBlogHeaderView; + private EventHeaderView mEventHeaderView; + private OSCApplication.ReadState mReadState; + public static boolean SAVE_ID = false;//是否保存新闻id + + public static SubFragment newInstance(SubTab subTab) { + SubFragment fragment = new SubFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable("sub_tab", subTab); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + mTab = (SubTab) bundle.getSerializable("sub_tab"); + assert mTab != null; + //CACHE_NAME = mTab.getToken(); + } + + @Override + protected void initWidget(View root) { + new SubPresenter(this, mTab); + super.initWidget(root); + } + + @Override + protected void initHeader() { + if (mTab.getBanner() != null) { + if (mTab.getBanner().getCatalog() == SubTab.BANNER_CATEGORY_NEWS) { + mHeaderView = new net.oschina.app.improve.main.header.NewsHeaderView(mContext, mTab.getBanner().getHref(), mTab.getToken() + "banner" + mTab.getType()); + } else if (mTab.getBanner().getCatalog() == SubTab.BANNER_CATEGORY_EVENT) { + mEventHeaderView = new EventHeaderView(mContext, getImgLoader(), mTab.getBanner().getHref(), mTab.getToken() + "banner" + mTab.getType()); + } else if (mTab.getBanner().getCatalog() == SubTab.BANNER_CATEGORY_BLOG) { + mBlogHeaderView = new BlogHeaderView(mContext, mTab.getBanner().getHref(), mTab.getToken() + "banner" + mTab.getType()); + } + } + } + + @Override + protected void hokeSetHeaderView() { + if (mTab.getBanner() != null) { + if (mTab.getBanner().getCatalog() == SubTab.BANNER_CATEGORY_NEWS) { + mAdapter.setHeaderView(mHeaderView); + } else if (mTab.getBanner().getCatalog() == SubTab.BANNER_CATEGORY_EVENT) { + mAdapter.setHeaderView(mEventHeaderView); + } else if (mTab.getBanner().getCatalog() == SubTab.BANNER_CATEGORY_BLOG) { + mAdapter.setHeaderView(mBlogHeaderView); + } + } + + mAdapter.setSystemTime(AppConfig.getAppConfig(getActivity()).get("system_time")); + if (mAdapter instanceof NewsSubAdapter) { + ((NewsSubAdapter) mAdapter).setTab(mTab); + } + mRefreshLayout.setBottomCount(2); + if (mPresenter != null) { + mPresenter.loadCache(); + } + } + + @Override + public void initData() { + mReadState = OSCApplication.getReadState("sub_list"); + super.initData(); + + } + + @Override + protected void onItemClick(SubBean sub, int position) { + if (!TDevice.hasWebView(mContext)) { + return; + } + switch (sub.getType()) { + case News.TYPE_SOFTWARE: + SoftwareDetailActivity.show(mContext, sub); + break; + case News.TYPE_QUESTION: + QuestionDetailActivity.show(mContext, sub); + break; + case News.TYPE_BLOG: + BlogDetailActivity.show(mContext, sub); + break; + case News.TYPE_TRANSLATE: + NewsDetailActivity.show(mContext, sub); + break; + case News.TYPE_EVENT: + EventDetailActivity.show(mContext, sub); + break; + case News.TYPE_NEWS: + NewsDetailActivity.show(mContext, sub); + break; + default: + UIHelper.showUrlRedirect(mContext, sub.getHref()); + break; + } + + mReadState.put(sub.getKey()); + mAdapter.updateItem(position); + } + + @Override + public void onScrollToBottom() { + if (mPresenter != null) { + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + mPresenter.onLoadMore(); + } + } + + @Override + public void onTabReselect() { + if (mRecyclerView != null) { + mRecyclerView.scrollToPosition(0); + mRefreshLayout.setRefreshing(true); + onRefreshing(); + } + } + + @Override + public void onRefreshing() { + super.onRefreshing(); + if (mHeaderView != null) + mHeaderView.requestBanner(); + if (mEventHeaderView != null) { + mEventHeaderView.requestBanner(); + } + } + + @Override + public void updateKey() { + if( mTab!= null && + mTab.getType() == 6 && + mAdapter.getItems().size() != 0){ + SubBean bean = mAdapter.getItem(0); + if(bean == null) + return; + OSCSharedPreference.getInstance().putTheNewsId(bean.getNewsId()); + if(SAVE_ID){ + OSCSharedPreference.getInstance().putLastNewsId(bean.getNewsId()); + ApiHttpClient.setHeaderNewsId(); + } + } + } + + @Override + public void onUpdateTime(String time) { + if (mAdapter == null) + return; + mAdapter.setSystemTime(time); + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + int mode = (mHeaderView != null || mEventHeaderView != null || mBlogHeaderView != null) + ? BaseRecyclerAdapter.BOTH_HEADER_FOOTER : BaseRecyclerAdapter.ONLY_FOOTER; + if (mTab.getType() == News.TYPE_BLOG) + return new BlogSubAdapter(getActivity(), mode); + else if (mTab.getType() == News.TYPE_EVENT) + return new EventSubAdapter(this, mode); + else if (mTab.getType() == News.TYPE_QUESTION) + return new QuestionSubAdapter(this, mode); + return new NewsSubAdapter(getActivity(), mode); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/main/sub/SubPresenter.java b/app/src/main/java/net/oschina/app/improve/main/sub/SubPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..e95bcb2131bb262dce2ce25f8c132209287266a5 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/sub/SubPresenter.java @@ -0,0 +1,144 @@ +package net.oschina.app.improve.main.sub; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.api.ApiHttpClient; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.SubTab; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.improve.utils.CacheManager; + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +import static net.oschina.app.improve.main.sub.SubFragment.SAVE_ID; + +/** + * 订阅接口 + * Created by huanghaibin on 2017/12/18. + */ + +class SubPresenter implements SubContract.Presenter { + private final SubContract.View mView; + private String mNextToken; + private final SubTab mTab; + private final String CACHE_NAME; + + SubPresenter(SubContract.View mView, SubTab tab) { + this.mView = mView; + this.mTab = tab; + CACHE_NAME = tab.getToken(); + this.mView.setPresenter(this); + + } + + @Override + public void loadCache() { + List items = CacheManager.readListJson(OSCApplication.getInstance(), CACHE_NAME, SubBean.class); + if (items != null) { + mView.onRefreshSuccess(items); + mView.updateKey(); + mView.onComplete(); + } + } + + @Override + public void onRefreshing() { + OSChinaApi.getSubscription(mTab.getHref(), "", new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> resultBean = new Gson().fromJson(responseString, getType()); + if (resultBean != null && resultBean.isSuccess()) { + PageBean pageBean = resultBean.getResult(); + mView.onUpdateTime(resultBean.getTime()); + mNextToken = pageBean.getNextPageToken(); + List list = pageBean.getItems(); + + CacheManager.saveToJson(OSCApplication.getInstance(), CACHE_NAME, list); + mView.onRefreshSuccess(list); + mView.updateKey(); + if (list.size() == 0) { + mView.showNotMore(); + } + + if(mTab.getType() == 6){ + Collections.sort(list); + SubBean bean = list.get(0); + OSCSharedPreference.getInstance().putTheNewsId(bean.getNewsId()); + if(SAVE_ID){ + OSCSharedPreference.getInstance().putLastNewsId(bean.getNewsId()); + ApiHttpClient.setHeaderNewsId(); + } + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + OSChinaApi.getSubscription(mTab.getHref(), mNextToken, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> resultBean = new Gson().fromJson(responseString, getType()); + if (resultBean != null && resultBean.isSuccess()) { + mView.onUpdateTime(resultBean.getTime()); + PageBean pageBean = resultBean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List list = pageBean.getItems(); + CacheManager.saveToJson(OSCApplication.getInstance(), CACHE_NAME, list); + mView.onLoadMoreSuccess(list); + mView.updateKey(); + if (list.size() == 0) { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + } + }); + } + + private Type getType() { + return new TypeToken>>() { + }.getType(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/subscription/BlogSubAdapter.java b/app/src/main/java/net/oschina/app/improve/main/subscription/BlogSubAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..79867947d83d583cd09c2a292ac3f01e80a41892 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/subscription/BlogSubAdapter.java @@ -0,0 +1,172 @@ +package net.oschina.app.improve.main.subscription; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.RecyclerView; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.ImageSpan; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; + +import java.util.HashMap; + +/** + * 新板博客栏目 + * Created by haibin + * on 2016/10/26. + */ + +public class BlogSubAdapter extends BaseRecyclerAdapter implements BaseRecyclerAdapter.OnLoadingHeaderCallBack { + + private OSCApplication.ReadState mReadState; + + public BlogSubAdapter(Context context, int mode) { + super(context, mode); + mReadState = OSCApplication.getReadState("sub_list"); + setOnLoadingHeaderCallBack(this); + } + + @Override + public RecyclerView.ViewHolder onCreateHeaderHolder(ViewGroup parent) { + return new HeaderViewHolder(mHeaderView); + } + + @Override + public void onBindHeaderHolder(RecyclerView.ViewHolder holder, int position) { + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new BlogViewHolder(mInflater.inflate(R.layout.item_list_sub_blog, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, SubBean item, int position) { + BlogViewHolder vh = (BlogViewHolder) holder; + + TextView title = vh.tv_title; + TextView content = vh.tv_description; + TextView see = vh.tv_view; + TextView answer = vh.tv_comment_count; + + String text = ""; + SpannableStringBuilder spannable = new SpannableStringBuilder(text); + + Resources resources = mContext.getResources(); + + boolean isToday = StringUtils.isSameDay(mSystemTime, item.getPubDate()); + if (isToday) { + spannable.append("[icon] "); + Drawable originate = resources.getDrawable(R.mipmap.ic_label_today); + if (originate != null) { + originate.setBounds(0, 0, originate.getIntrinsicWidth(), originate.getIntrinsicHeight()); + } + ImageSpan imageSpan = new ImageSpan(originate, ImageSpan.ALIGN_BOTTOM); + spannable.setSpan(imageSpan, 0, 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + + if (item.isOriginal()) { + spannable.append("[icon] "); + Drawable originate = resources.getDrawable(R.mipmap.ic_label_originate); + if (originate != null) { + originate.setBounds(0, 0, originate.getIntrinsicWidth(), originate.getIntrinsicHeight()); + } + ImageSpan imageSpan = new ImageSpan(originate, ImageSpan.ALIGN_BOTTOM); + spannable.setSpan(imageSpan, isToday ? 7 : 0, isToday ? 13 : 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } else { + spannable.append("[icon] "); + Drawable originate = resources.getDrawable(R.mipmap.ic_label_reprint); + if (originate != null) { + originate.setBounds(0, 0, originate.getIntrinsicWidth(), originate.getIntrinsicHeight()); + } + ImageSpan imageSpan = new ImageSpan(originate, ImageSpan.ALIGN_BOTTOM); + spannable.setSpan(imageSpan, isToday ? 7 : 0, isToday ? 13 : 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + + if (item.isRecommend()) { + spannable.append("[icon] "); + Drawable recommend = resources.getDrawable(R.mipmap.ic_label_recommend); + if (recommend != null) { + recommend.setBounds(0, 0, recommend.getIntrinsicWidth(), recommend.getIntrinsicHeight()); + } + ImageSpan imageSpan = new ImageSpan(recommend, ImageSpan.ALIGN_BOTTOM); + spannable.setSpan(imageSpan, isToday ? 14 : 7, isToday ? 20 : 13, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + + title.setText(spannable.append(item.getTitle())); + + String body = item.getBody(); + if (!TextUtils.isEmpty(body)) { + body = body.replaceFirst("\\s*|\t|\n", ""); + if (!TextUtils.isEmpty(body)) { + content.setText(body); + content.setVisibility(View.VISIBLE); + } else { + content.setVisibility(View.GONE); + } + } else { + content.setVisibility(View.INVISIBLE); + } + + if (mReadState.already(item.getKey())) { + title.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + content.setTextColor(TDevice.getColor(resources, R.color.text_secondary_color)); + } else { + title.setTextColor(TDevice.getColor(resources, R.color.text_title_color)); + content.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + } + + Author author = item.getAuthor(); + String authorName; + if (author != null && !TextUtils.isEmpty(authorName = author.getName())) { + vh.tv_time.setText(String.format("@%s %s", + (authorName.length() > 9 ? authorName.substring(0, 9) : authorName), + StringUtils.formatSomeAgo(item.getPubDate()))); + } else { + vh.tv_time.setText(StringUtils.formatSomeAgo(item.getPubDate())); + } + + + see.setText(String.valueOf(item.getStatistics().getView())); + answer.setText(String.valueOf(item.getStatistics().getComment())); + HashMap extra = item.getExtra(); + if (extra != null && getExtraBool(extra.get("isPrivate"))) { + vh.tv_type.setVisibility(View.VISIBLE); + } else { + vh.tv_type.setVisibility(View.GONE); + } + } + + private boolean getExtraBool(Object object) { + return object == null ? false : Boolean.valueOf(object.toString()); + } + + private static class BlogViewHolder extends RecyclerView.ViewHolder { + TextView tv_title, tv_description, tv_time, tv_comment_count, tv_view, tv_type; + LinearLayout ll_title; + + private BlogViewHolder(View itemView) { + super(itemView); + tv_title = (TextView) itemView.findViewById(R.id.tv_title); + tv_type = (TextView) itemView.findViewById(R.id.tv_type); + tv_description = (TextView) itemView.findViewById(R.id.tv_description); + tv_time = (TextView) itemView.findViewById(R.id.tv_time); + tv_comment_count = (TextView) itemView.findViewById(R.id.tv_info_comment); + tv_view = (TextView) itemView.findViewById(R.id.tv_info_view); + ll_title = (LinearLayout) itemView.findViewById(R.id.ll_title); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/subscription/EventSubAdapter.java b/app/src/main/java/net/oschina/app/improve/main/subscription/EventSubAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..1de0861ac2d5251b057b8a14dc58eeee68345b22 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/subscription/EventSubAdapter.java @@ -0,0 +1,143 @@ +package net.oschina.app.improve.main.subscription; + +import android.content.res.Resources; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Event; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; + +import java.util.List; +import java.util.Map; + +/** + * 新版活动栏目 + * Created by haibin + * on 2016/10/27. + */ + +public class EventSubAdapter extends BaseGeneralRecyclerAdapter implements BaseRecyclerAdapter.OnLoadingHeaderCallBack { + private OSCApplication.ReadState mReadState; + + public EventSubAdapter(Callback callback, int mode) { + super(callback, mode); + mReadState = OSCApplication.getReadState("sub_list"); + setOnLoadingHeaderCallBack(this); + } + + @Override + public RecyclerView.ViewHolder onCreateHeaderHolder(ViewGroup parent) { + return new HeaderViewHolder(mHeaderView); + } + + @Override + public void onBindHeaderHolder(RecyclerView.ViewHolder holder, int position) { + + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new EventViewHolder(mInflater.inflate(R.layout.item_list_sub_event, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, SubBean item, int position) { + EventViewHolder vh = (EventViewHolder) holder; + vh.tv_event_title.setText(item.getTitle()); + List images = item.getImages(); + if (images != null && images.size() > 0) { + mCallBack.getImgLoader() + .load(images.get(0).getHref()) + .placeholder(R.mipmap.event_cover_default) + .into(vh.iv_event); + } else { + vh.iv_event.setImageResource(R.mipmap.event_cover_default); + } + + Resources resources = mContext.getResources(); + + Map extras = item.getExtra(); + if (extras != null) { + vh.tv_event_pub_date.setText(StringUtils.getDateString(getExtraString(extras.get("eventStartDate")))); + + switch (getExtraInt(extras.get("eventStatus"))) { + case Event.STATUS_END: + setText(vh.tv_event_state, R.string.event_status_end, R.drawable.bg_event_end, 0x1a000000); + setTextColor(vh.tv_event_title, TDevice.getColor(resources, R.color.light_gray)); + break; + case Event.STATUS_ING: + setText(vh.tv_event_state, R.string.event_status_ing, R.drawable.bg_event_ing, 0xFF24cf5f); + break; + case Event.STATUS_SING_UP: + setText(vh.tv_event_state, R.string.event_status_sing_up, R.drawable.bg_event_end, 0x1a000000); + setTextColor(vh.tv_event_title, TDevice.getColor(resources, R.color.light_gray)); + break; + } + int typeStr = R.string.osc_site; + switch (getExtraInt(extras.get("eventType"))) { + case Event.EVENT_TYPE_OSC: + typeStr = R.string.event_type_osc; + break; + case Event.EVENT_TYPE_TEC: + typeStr = R.string.event_type_tec; + break; + case Event.EVENT_TYPE_OTHER: + typeStr = R.string.event_type_other; + break; + case Event.EVENT_TYPE_OUTSIDE: + typeStr = R.string.event_type_outside; + break; + } + vh.tv_event_type.setText(typeStr); + } + + vh.tv_event_title.setTextColor(TDevice.getColor(resources, + mReadState.already(item.getKey()) + ? R.color.text_desc_color : R.color.text_title_color)); + + } + + private void setText(TextView tv, int textRes, int bgRes, int textColor) { + tv.setText(textRes); + tv.setVisibility(View.VISIBLE); + tv.setBackgroundResource(bgRes); + tv.setTextColor(textColor); + } + + private void setTextColor(TextView tv, int textColor) { + tv.setTextColor(textColor); + tv.setVisibility(View.VISIBLE); + } + + private static class EventViewHolder extends RecyclerView.ViewHolder { + TextView tv_event_title, tv_description, tv_event_pub_date, tv_event_state, tv_event_type; + ImageView iv_event; + + EventViewHolder(View itemView) { + super(itemView); + tv_event_title = (TextView) itemView.findViewById(R.id.tv_event_title); + tv_event_state = (TextView) itemView.findViewById(R.id.tv_event_state); + tv_event_type = (TextView) itemView.findViewById(R.id.tv_event_type); + tv_description = (TextView) itemView.findViewById(R.id.tv_description); + tv_event_pub_date = (TextView) itemView.findViewById(R.id.tv_event_pub_date); + iv_event = (ImageView) itemView.findViewById(R.id.iv_event); + } + } + + private String getExtraString(Object object) { + return object == null ? "" : object.toString(); + } + + private int getExtraInt(Object object) { + return object == null ? 0 : Double.valueOf(object.toString()).intValue(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/subscription/NewsSubAdapter.java b/app/src/main/java/net/oschina/app/improve/main/subscription/NewsSubAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..43a800068d4c36a55dde9c5b4b46ea1422ced6a9 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/subscription/NewsSubAdapter.java @@ -0,0 +1,127 @@ +package net.oschina.app.improve.main.subscription; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.RecyclerView; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.ImageSpan; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.SubTab; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; + +/** + * 新版新闻订阅栏目 + * Created by haibin + * on 2016/10/26. + */ + +public class NewsSubAdapter extends BaseRecyclerAdapter implements BaseRecyclerAdapter.OnLoadingHeaderCallBack { + private OSCApplication.ReadState mReadState; + private SubTab mTab; + + public NewsSubAdapter(Context context, int mode) { + super(context, mode); + mReadState = OSCApplication.getReadState("sub_list"); + setOnLoadingHeaderCallBack(this); + } + + public void setTab(SubTab tab) { + this.mTab = tab; + } + + @Override + public RecyclerView.ViewHolder onCreateHeaderHolder(ViewGroup parent) { + return new HeaderViewHolder(mHeaderView); + } + + @Override + public void onBindHeaderHolder(RecyclerView.ViewHolder holder, int position) { + + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new NewsViewHolder(mInflater.inflate(R.layout.item_list_sub_news, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, SubBean item, int position) { + NewsViewHolder vh = (NewsViewHolder) holder; + + Resources resources = mContext.getResources(); + + if (mReadState.already(item.getKey())) { + vh.tv_title.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + vh.tv_description.setTextColor(TDevice.getColor(resources, R.color.text_secondary_color)); + } else { + vh.tv_title.setTextColor(TDevice.getColor(resources, R.color.text_title_color)); + vh.tv_description.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + } + + vh.tv_description.setText(TextUtils.isEmpty(item.getBody()) ? + item.getBody() : item.getBody().replaceFirst("\\s*|\t|\n", "")); + Author author = item.getAuthor(); + String authorName; + if (author != null && !TextUtils.isEmpty(authorName = author.getName())) { + authorName = authorName.trim(); + vh.tv_time.setText(String.format("@%s %s", + (authorName.length() > 9 ? authorName.substring(0, 9) : authorName), + StringUtils.formatSomeAgo(item.getPubDate().trim()))); + } else { + vh.tv_time.setText(StringUtils.formatSomeAgo(item.getPubDate().trim())); + } + + if (StringUtils.isSameDay(mSystemTime, item.getPubDate()) && mTab.getSubtype() != 2 && item.getType() != 7) { + + String text = "[icon] " + item.getTitle(); + Drawable drawable = resources.getDrawable(R.mipmap.ic_label_today); + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM); + + SpannableString spannable = new SpannableString(text); + spannable.setSpan(imageSpan, 0, 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + vh.tv_title.setText(spannable); + } else { + vh.tv_title.setText(item.getTitle()); + } + if (item.getType() == 0) { + vh.ll_info.setVisibility(View.GONE); + } else { + vh.ll_info.setVisibility(View.VISIBLE); + vh.tv_comment_count.setText(String.valueOf(item.getStatistics().getComment())); + vh.tv_view_count.setText(String.valueOf(item.getStatistics().getView())); + } + } + + private static class NewsViewHolder extends RecyclerView.ViewHolder { + TextView tv_title, tv_description, tv_time, tv_comment_count, tv_view_count; + LinearLayout ll_title, ll_info; + + NewsViewHolder(View itemView) { + super(itemView); + tv_title = (TextView) itemView.findViewById(R.id.tv_title); + tv_description = (TextView) itemView.findViewById(R.id.tv_description); + tv_time = (TextView) itemView.findViewById(R.id.tv_time); + ll_title = (LinearLayout) itemView.findViewById(R.id.ll_title); + + ll_info = (LinearLayout) itemView.findViewById(R.id.lay_info); + tv_comment_count = (TextView) ll_info.findViewById(R.id.tv_info_comment); + tv_view_count = (TextView) ll_info.findViewById(R.id.tv_info_view); + tv_view_count.setVisibility(View.GONE); + ll_info.findViewById(R.id.iv_info_view).setVisibility(View.GONE); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/subscription/QuestionSubAdapter.java b/app/src/main/java/net/oschina/app/improve/main/subscription/QuestionSubAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..064bfbaa7899c9f2e3995e943fbe3c49fcf9cdf0 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/subscription/QuestionSubAdapter.java @@ -0,0 +1,103 @@ +package net.oschina.app.improve.main.subscription; + +import android.content.res.Resources; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; + +/** + * 新版栏目问答 + * Created by haibin + * on 2016/10/27. + */ + +public class QuestionSubAdapter extends BaseGeneralRecyclerAdapter implements BaseRecyclerAdapter.OnLoadingHeaderCallBack { + private OSCApplication.ReadState mReadState; + + public QuestionSubAdapter(Callback callback, int mode) { + super(callback, mode); + mReadState = OSCApplication.getReadState("sub_list"); + setOnLoadingHeaderCallBack(this); + } + + @Override + public RecyclerView.ViewHolder onCreateHeaderHolder(ViewGroup parent) { + return new HeaderViewHolder(mHeaderView); + } + + @Override + public void onBindHeaderHolder(RecyclerView.ViewHolder holder, int position) { + + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new QuestionViewHolder(mInflater.inflate(R.layout.item_list_sub_question, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, SubBean item, int position) { + QuestionViewHolder vh = (QuestionViewHolder) holder; + + Author author = item.getAuthor(); + if (author == null) { + vh.iv_question.setup(0, "匿名用户", ""); + } else { + vh.iv_question.setup(author); + } + + vh.tv_question_title.setText(item.getTitle()); + vh.tv_question_content.setText(TextUtils.isEmpty(item.getBody()) ? + item.getBody() : item.getBody().replaceFirst("\\s*|\t|\n", "")); + + Resources resources = mContext.getResources(); + + if (mReadState.already(item.getKey())) { + vh.tv_question_title.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + vh.tv_question_content.setTextColor(TDevice.getColor(resources, R.color.text_secondary_color)); + } else { + vh.tv_question_title.setTextColor(TDevice.getColor(resources, R.color.text_title_color)); + vh.tv_question_content.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + } + + String authorName; + if (author != null && !TextUtils.isEmpty(authorName = author.getName())) { + vh.tv_name.setText(String.format("@%s", authorName.length() > 9 ? authorName.substring(0, 9) : authorName)); + vh.tv_time.setText(StringUtils.formatSomeAgo(item.getPubDate())); + } else { + vh.tv_name.setText(""); + vh.tv_time.setText(StringUtils.formatSomeAgo(item.getPubDate())); + } + + vh.tv_view.setText(String.valueOf(item.getStatistics().getView())); + vh.tv_comment_count.setText(String.valueOf(item.getStatistics().getComment())); + } + + private static class QuestionViewHolder extends RecyclerView.ViewHolder { + TextView tv_question_title, tv_question_content, tv_name, tv_time, tv_comment_count, tv_view; + PortraitView iv_question; + + QuestionViewHolder(View itemView) { + super(itemView); + tv_question_title = (TextView) itemView.findViewById(R.id.tv_question_title); + tv_question_content = (TextView) itemView.findViewById(R.id.tv_question_content); + tv_name = (TextView) itemView.findViewById(R.id.tv_name); + tv_time = (TextView) itemView.findViewById(R.id.tv_time); + tv_comment_count = (TextView) itemView.findViewById(R.id.tv_info_comment); + tv_view = (TextView) itemView.findViewById(R.id.tv_info_view); + iv_question = (PortraitView) itemView.findViewById(R.id.iv_question); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/subscription/SubFragment.java b/app/src/main/java/net/oschina/app/improve/main/subscription/SubFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..c0f01e355cae5c1f73f6823c3dce4a108180bdab --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/subscription/SubFragment.java @@ -0,0 +1,195 @@ +package net.oschina.app.improve.main.subscription; + +import android.os.Bundle; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.AppConfig; +import net.oschina.app.OSCApplication; +import net.oschina.app.api.ApiHttpClient; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseGeneralRecyclerFragment; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.SubTab; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.main.banner.EventHeaderView; +import net.oschina.app.improve.main.header.BlogHeaderView; +import net.oschina.app.improve.main.header.HeaderView; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; + +/** + * Created by haibin + * on 2016/10/26. + */ + +public class SubFragment extends BaseGeneralRecyclerFragment { + private SubTab mTab; + private HeaderView mHeaderView; + private BlogHeaderView mBlogHeaderView; + private EventHeaderView mEventHeaderView; + private OSCApplication.ReadState mReadState; + public static boolean SAVE_ID = false;//是否保存新闻id + + public static SubFragment newInstance(SubTab subTab) { + SubFragment fragment = new SubFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable("sub_tab", subTab); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + mTab = (SubTab) bundle.getSerializable("sub_tab"); + assert mTab != null; + CACHE_NAME = mTab.getToken(); + } + + @Override + public void initData() { + mReadState = OSCApplication.getReadState("sub_list"); + if (mTab.getBanner() != null) { +// mHeaderView = mTab.getBanner().getCatalog() == SubTab.BANNER_CATEGORY_NEWS ? +// new NewsHeaderView(mContext, getImgLoader(), mTab.getBanner().getHref(), mTab.getToken() + "banner" + mTab.getType()) : +// new EventHeaderView(mContext, getImgLoader(), mTab.getBanner().getHref(), mTab.getToken() + "banner" + mTab.getType()); + if (mTab.getBanner().getCatalog() == SubTab.BANNER_CATEGORY_NEWS) { + mHeaderView = new net.oschina.app.improve.main.header.NewsHeaderView(mContext, mTab.getBanner().getHref(), mTab.getToken() + "banner" + mTab.getType()); + } else if (mTab.getBanner().getCatalog() == SubTab.BANNER_CATEGORY_EVENT) { + mEventHeaderView = new EventHeaderView(mContext, getImgLoader(), mTab.getBanner().getHref(), mTab.getToken() + "banner" + mTab.getType()); + } else if (mTab.getBanner().getCatalog() == SubTab.BANNER_CATEGORY_BLOG) { + mBlogHeaderView = new BlogHeaderView(mContext, mTab.getBanner().getHref(), mTab.getToken() + "banner" + mTab.getType()); + } + } + super.initData(); + if (mTab.getBanner() != null) { + if (mTab.getBanner().getCatalog() == SubTab.BANNER_CATEGORY_NEWS) { + mAdapter.setHeaderView(mHeaderView); + } else if (mTab.getBanner().getCatalog() == SubTab.BANNER_CATEGORY_EVENT) { + mAdapter.setHeaderView(mEventHeaderView); + } else if (mTab.getBanner().getCatalog() == SubTab.BANNER_CATEGORY_BLOG) { + mAdapter.setHeaderView(mBlogHeaderView); + } + } + + mAdapter.setSystemTime(AppConfig.getAppConfig(getActivity()).get("system_time")); + if (mAdapter instanceof NewsSubAdapter) { + ((NewsSubAdapter) mAdapter).setTab(mTab); + } + } + + @Override + public void onItemClick(int position, long itemId) { + if (!TDevice.hasWebView(mContext)) + return; + SubBean sub = mAdapter.getItem(position); + if (sub == null) + return; + switch (sub.getType()) { + case News.TYPE_SOFTWARE: + SoftwareDetailActivity.show(mContext, sub); + break; + case News.TYPE_QUESTION: + QuestionDetailActivity.show(mContext, sub); + break; + case News.TYPE_BLOG: + BlogDetailActivity.show(mContext, sub); + break; + case News.TYPE_TRANSLATE: + NewsDetailActivity.show(mContext, sub); + break; + case News.TYPE_EVENT: + EventDetailActivity.show(mContext, sub); + break; + case News.TYPE_NEWS: + NewsDetailActivity.show(mContext, sub); + break; + default: + UIHelper.showUrlRedirect(mContext, sub.getHref()); + break; + } + + mReadState.put(sub.getKey()); + mAdapter.updateItem(position); + } + + @Override + public void onRefreshing() { + super.onRefreshing(); + if (mHeaderView != null) + mHeaderView.requestBanner(); + if (mEventHeaderView != null) { + mEventHeaderView.requestBanner(); + } + } + + @Override + protected void requestData() { + OSChinaApi.getSubscription(mTab.getHref(), isRefreshing ? null : mBean.getNextPageToken(), mHandler); + } + + @Override + protected void setListData(ResultBean> resultBean) { + super.setListData(resultBean); + mAdapter.setSystemTime(resultBean.getTime()); + if (mTab != null && + mTab.getType() == 6 && + mAdapter.getItems().size() != 0) { + SubBean bean = mAdapter.getItem(0); + if (bean == null) + return; + OSCSharedPreference.getInstance().putTheNewsId(bean.getNewsId()); + if (SAVE_ID) { + OSCSharedPreference.getInstance().putLastNewsId(bean.getNewsId()); + ApiHttpClient.setHeaderNewsId(); + } + } + } + + + private SubBean getLastSubBean(List beans) { + if (beans == null || beans.size() == 0) + return null; + Collections.sort(beans); + return beans.get(0); + } + + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + int mode = (mHeaderView != null || mEventHeaderView != null || mBlogHeaderView != null) + ? BaseRecyclerAdapter.BOTH_HEADER_FOOTER : BaseRecyclerAdapter.ONLY_FOOTER; + if (mTab.getType() == News.TYPE_BLOG) + return new BlogSubAdapter(getActivity(), mode); + else if (mTab.getType() == News.TYPE_EVENT) + return new EventSubAdapter(this, mode); + else if (mTab.getType() == News.TYPE_QUESTION) + return new QuestionSubAdapter(this, mode); + return new NewsSubAdapter(getActivity(), mode); + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } + + @Override + protected Class getCacheClass() { + return SubBean.class; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/DataFormat.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/DataFormat.java new file mode 100644 index 0000000000000000000000000000000000000000..2ad16f052b4efefadeed038fcfd4ecfd7d9ede18 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/DataFormat.java @@ -0,0 +1,55 @@ +package net.oschina.app.improve.main.synthesize; + +import android.text.TextUtils; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +/** + * 头条时间格式化 + * Created by huanghaibin on 2017/11/7. + */ + +public final class DataFormat { + + private static final SimpleDateFormat CUR_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); + + public static String parsePubDate(String pubDate) { + if (TextUtils.isEmpty(pubDate) || pubDate.length() < 8) + return pubDate; + int year = parseInt(pubDate.substring(0, 4)); + int month = parseInt(pubDate.substring(4, 6)); + int day = parseInt(pubDate.substring(6, 8)); + String date = String.format("%s-%s-%s", year, + pubDate.substring(4, 6), + pubDate.substring(6, 8)); + if (isToday(date)) { + return "今天"; + } + if (isYesterday(year, month, day)) + return "昨天"; + return date; + } + + private static int parseInt(String intStr) { + try { + return Integer.parseInt(intStr); + } catch (Exception e) { + return 0; + } + } + + private static boolean isToday(String pubDate) { + String today = CUR_FORMAT.format(new Date()); + return pubDate.equalsIgnoreCase(today); + } + + private static boolean isYesterday(int year, int month, int day) { + Calendar mCurrentDate = Calendar.getInstance(); + mCurrentDate.set(year, month -1, day , 0, 0, 0); + long delta = new Date().getTime() - mCurrentDate.getTimeInMillis(); + return delta > 0 && delta <= 48L * 3600000L; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/SynthesizeFragment.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/SynthesizeFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..ab37779b53945c31ead3a2713ea406ff654ad9aa --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/SynthesizeFragment.java @@ -0,0 +1,253 @@ +package net.oschina.app.improve.main.synthesize; + + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.view.ViewPager; +import android.view.View; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.api.ApiHttpClient; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.base.fragments.BaseGeneralListFragment; +import net.oschina.app.improve.base.fragments.BaseGeneralRecyclerFragment; +import net.oschina.app.improve.base.fragments.BasePagerFragment; +import net.oschina.app.improve.bean.SubTab; +import net.oschina.app.improve.main.sub.SubFragment; +import net.oschina.app.improve.main.synthesize.article.ArticleFragment; +import net.oschina.app.improve.main.synthesize.english.EnglishArticleFragment; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.improve.notice.NoticeBean; +import net.oschina.app.improve.notice.NoticeManager; +import net.oschina.app.improve.search.v2.SearchActivity; +import net.oschina.app.interf.OnTabReselectListener; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 新版综合界面 + * Created by huanghaibin on 2017/10/23. + */ + +public class SynthesizeFragment extends BasePagerFragment implements + NoticeManager.NoticeNotify, + OnTabReselectListener, + View.OnClickListener { + + private int mCurrentItem; + private TextView mTextCount; + + @Bind(R.id.viewStatusBar) + View mStatusBar; + + public static SynthesizeFragment newInstance() { + return new SynthesizeFragment(); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_synthesize; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + //setStatusBarPadding(); + if (BaseActivity.hasSetStatusBarColor) { + mStatusBar.setBackgroundColor(getResources().getColor(R.color.status_bar_color)); + } + NoticeManager.bindNotify(this); + mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + setTitleText(false, mCurrentItem); + setTitleText(true, position); + mCurrentItem = position; + SubFragment.SAVE_ID = position == 2; + if (SubFragment.SAVE_ID) { + OSCSharedPreference.getInstance().putLastNewsId(OSCSharedPreference.getInstance().getTheNewsId()); + if (mRoot != null && mAdapter != null && hasCount()) { + mRoot.postDelayed(new Runnable() { + @Override + public void run() { + Fragment fragment = mAdapter.getCurFragment(); + if (fragment != null && fragment instanceof SubFragment) { + ((SubFragment) fragment).onRefreshing(); + } + } + }, 1000); + } + } + ApiHttpClient.setHeaderNewsId(); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + mViewPager.setCurrentItem(1); + } + + private boolean hasCount() { + return mTextCount != null && !"0".equals(mTextCount.getText().toString()); + } + + private void setTitleText(boolean isSelected, int position) { + TabLayout.Tab tab = mTabLayout.getTabAt(position); + if (tab == null) return; + View view = tab.getCustomView(); + if (view == null) return; + TextView textView = (TextView) view.findViewById(R.id.tv_title); + textView.setTextColor(isSelected ? 0xff24cf5f : 0xff6A6A6A); + } + + @Override + protected void setupTabView() { + for (int i = 0; i < mTabLayout.getTabCount(); i++) { + TabLayout.Tab tab = mTabLayout.getTabAt(i); + if (tab != null) { + tab.setCustomView(getTabView(i)); + if (tab.getCustomView() != null) { + View tabView = (View) tab.getCustomView().getParent(); + tabView.setTag(i); + tabView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + } + }); + } + } + } + } + + @Override + public void onNoticeArrived(NoticeBean bean) { + if (mTextCount == null || bean == null) + return; + if (bean.getNewsCount() == 0) { + mTextCount.setVisibility(View.GONE); + } else { + mTextCount.setVisibility(View.VISIBLE); + mTextCount.setText(String.valueOf(bean.getNewsCount())); + } + } + + + @SuppressLint("InflateParams") + private View getTabView(int i) { + View view = mInflater.inflate(R.layout.tab_synthesize, null); + TextView tv_title = (TextView) view.findViewById(R.id.tv_title); + TextView tv_count = (TextView) view.findViewById(R.id.tv_count); + if (i == 0) { + tv_title.setTextColor(0xff24cf5f); + } + if (i == 2) { + mTextCount = tv_count; + } + tv_title.setText(mAdapter.getPageTitle(i)); + return view; + } + + @OnClick({R.id.iv_search}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.iv_search: + //SearchActivity.show(mContext); + SearchActivity.show(getContext()); + break; + } + } + + @Override + public void onTabReselect() { + if (mViewPager != null && mViewPager.getAdapter() != null) { + BasePagerFragment.Adapter pagerAdapter = (BasePagerFragment.Adapter) mViewPager.getAdapter(); + Fragment fragment = pagerAdapter.getCurFragment(); + if (fragment != null) { + if (fragment instanceof BaseGeneralListFragment) + ((BaseGeneralListFragment) fragment).onTabReselect(); + else if (fragment instanceof BaseGeneralRecyclerFragment) + ((BaseGeneralRecyclerFragment) fragment).onTabReselect(); + else if (fragment instanceof ArticleFragment) + ((ArticleFragment) fragment).onTabReselect(); + else if (fragment instanceof EnglishArticleFragment) + ((EnglishArticleFragment) fragment).onTabReselect(); + else if (fragment instanceof SubFragment) { + ((SubFragment) fragment).onTabReselect(); + } + } + } + } + + @Override + protected List getFragments() { + List fragments = new ArrayList<>(); + fragments.add(EnglishArticleFragment.newInstance()); + fragments.add(ArticleFragment.newInstance()); + fragments.add(getSubFragment(6, + "开源资讯", + "https://www.oschina.net/action/apiv2/sub_list?token=e6142fa662bc4bf21083870a957fbd20", + 1, + "e6142fa662bc4bf21083870a957fbd20")); + fragments.add(getSubFragment(2, + "技术问答", + "https://www.oschina.net/action/apiv2/sub_list?token=98d04eb58a1d12b75d254deecbc83790", + 3, + "98d04eb58a1d12b75d254deecbc83790")); + fragments.add(getSubFragment(3, + "推荐博客", + "https://www.oschina.net/action/apiv2/sub_list?token=df985be3c5d5449f8dfb47e06e098ef9", + 4, + "df985be3c5d5449f8dfb47e06e098ef9")); + + return fragments; + } + + @Override + protected String[] getTitles() { + return getResources().getStringArray(R.array.synthesize_titles); + } + + private SubFragment getSubFragment(int type, String title, String url, int subType, String token) { + SubTab tab = new SubTab(); + if (type == 3) { + SubTab.Banner banner = tab.new Banner(); + banner.setCatalog(4); + banner.setHref("https://www.oschina.net/action/apiv2/banner?catalog=4"); + tab.setBanner(banner); + } + tab.setType(type); + tab.setFixed(false); + tab.setName(title); + tab.setNeedLogin(false); + tab.setHref(url); + tab.setSubtype(subType); + tab.setToken(token); + + Bundle bundle = new Bundle(); + bundle.putSerializable("sub_tab", tab); + return SubFragment.newInstance(tab); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + NoticeManager.bindNotify(this); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/TypeFormat.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/TypeFormat.java new file mode 100644 index 0000000000000000000000000000000000000000..0dc9469a2caea509d6eb675cf5569f1d3fb59158 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/TypeFormat.java @@ -0,0 +1,35 @@ +package net.oschina.app.improve.main.synthesize; + +import android.text.TextUtils; + +import net.oschina.app.improve.bean.Article; + +/** + * 类型 + * Created by huanghaibin on 2017/12/11. + */ + +public final class TypeFormat { + public static boolean isGit(Article article) { + String url = article.getUrl(); + return !TextUtils.isEmpty(url) && (url.startsWith("https://gitee.com/") || url.startsWith("https://git.oschina.net/")); + } + + public static boolean isZB(Article article) { + String url = article.getUrl(); + return !TextUtils.isEmpty(url) && (url.startsWith("https://zb.oschina.net/")); + } + + + public static String formatUrl(Article article){ + return article.getUrl().contains("?") ? + String.format("%s&%s",article.getUrl(),"utm_source=oschina-app") : + String.format("%s?%s",article.getUrl(),"utm_source=oschina-app"); + } + + public static String formatUrl(String url){ + return url.contains("?") ? + String.format("%s&%s",url,"utm_source=oschina-app") : + String.format("%s?%s",url,"utm_source=oschina-app"); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/article/ArticleAdapter.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/article/ArticleAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..cfe0cc7bbab49656922df00507c31bdca9f9ee7e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/article/ArticleAdapter.java @@ -0,0 +1,277 @@ +package net.oschina.app.improve.main.synthesize.article; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.RecyclerView; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.ImageSpan; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.main.synthesize.DataFormat; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.media.Util; +import net.oschina.app.util.TDevice; + +/** + * 头条数据 + * Created by huanghaibin on 2017/10/23. + */ + +public class ArticleAdapter extends BaseRecyclerAdapter
    implements BaseRecyclerAdapter.OnLoadingHeaderCallBack { + + private static final int VIEW_TYPE_NOT_IMG = 1; + private static final int VIEW_TYPE_ONE_IMG = 2; + private static final int VIEW_TYPE_THREE_IMG = 3; + + private static int WIDTH = 0; + private static final String FORMAT = "!/both/330x246/quality/100"; + private RequestManager mLoader; + private OSCApplication.ReadState mReadState; + + public ArticleAdapter(Context context, int mode) { + super(context, mode); + mReadState = OSCApplication.getReadState("sub_list"); + setOnLoadingHeaderCallBack(this); + mLoader = Glide.with(mContext); + WIDTH = (Util.getScreenWidth(context) - Util.dipTopx(context, 48)) / 3; + } + + @Override + public int getItemViewType(int position) { + Article article = getItem(position); + if (article != null) { + String imgs[] = article.getImgs(); + if (imgs == null || imgs.length == 0) + return VIEW_TYPE_NOT_IMG; + if (imgs.length < 3) + return VIEW_TYPE_ONE_IMG; + return VIEW_TYPE_THREE_IMG; + } + return super.getItemViewType(position); + } + + @Override + public RecyclerView.ViewHolder onCreateHeaderHolder(ViewGroup parent) { + return new HeaderHolder(mHeaderView); + } + + @Override + public void onBindHeaderHolder(RecyclerView.ViewHolder holder, int position) { + + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + if (type == VIEW_TYPE_NOT_IMG) { + return new TextHolder(mInflater.inflate(R.layout.item_list_article_not_img, parent, false)); + } else if (type == VIEW_TYPE_ONE_IMG) { + return new OneImgHolder(mInflater.inflate(R.layout.item_list_article_one_img, parent, false)); + } else { + return new ThreeImgHolder(mInflater.inflate(R.layout.item_list_article_three_img, parent, false)); + } + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Article item, int position) { + int type = getItemViewType(position); + Resources resources = mContext.getResources(); + String sourceName = item.getType() != 0 && item.getType()!= Article.TYPE_ENGLISH ? "开源中国" : item.getSource(); + String desc = TextUtils.isEmpty(item.getDesc()) ? "" : item.getDesc().replaceFirst("\\s*|\t|\n", ""); + switch (type) { + case VIEW_TYPE_NOT_IMG: + TextHolder h = (TextHolder) holder; + setTag(h.mTextTitle, h.mImageTag, item); + h.mTextDesc.setText(desc); + h.mTextDesc.setVisibility(TextUtils.isEmpty(desc) ? View.GONE : View.VISIBLE); + h.mTextTime.setText(DataFormat.parsePubDate(item.getPubDate())); + h.mTextAuthor.setText(TextUtils.isEmpty(item.getAuthorName()) ? "匿名" : item.getAuthorName()); + h.mTextOrigin.setText(TextUtils.isEmpty(item.getAuthorName()) ? sourceName : item.getAuthorName()); + h.mTextCommentCount.setText(String.valueOf(item.getCommentCount())); + if (mReadState.already(item.getKey())) { + h.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + h.mTextDesc.setTextColor(TDevice.getColor(resources, R.color.text_secondary_color)); + } else { + h.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_title_color)); + h.mTextDesc.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + } + break; + case VIEW_TYPE_ONE_IMG: + OneImgHolder h1 = (OneImgHolder) holder; + setTag(h1.mTextTitle, h1.mImageTag, item); + h1.mFrameImage.getLayoutParams().width = WIDTH; + h1.mTextTime.setText(DataFormat.parsePubDate(item.getPubDate())); + h1.mTextAuthor.setText(TextUtils.isEmpty(item.getAuthorName()) ? "匿名" : item.getAuthorName()); + h1.mTextOrigin.setText(TextUtils.isEmpty(item.getAuthorName()) ? sourceName : item.getAuthorName()); + h1.mTextCommentCount.setText(String.valueOf(item.getCommentCount())); + mLoader.load(item.getImgs()[0] + FORMAT) + .fitCenter() + .error(R.mipmap.ic_split_graph) + .into(h1.mImageView); + if (mReadState.already(item.getKey())) { + h1.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + } else { + h1.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_title_color)); + } + break; + case VIEW_TYPE_THREE_IMG: + ThreeImgHolder h2 = (ThreeImgHolder) holder; + setTag(h2.mTextTitle, h2.mImageTag, item); + h2.mTextTime.setText(DataFormat.parsePubDate(item.getPubDate())); + h2.mTextAuthor.setText(TextUtils.isEmpty(item.getAuthorName()) ? "匿名" : item.getAuthorName()); + h2.mTextOrigin.setText(TextUtils.isEmpty(item.getAuthorName()) ? sourceName : item.getAuthorName()); + h2.mTextCommentCount.setText(String.valueOf(item.getCommentCount())); + mLoader.load(item.getImgs()[0] + FORMAT) + .fitCenter() + .error(R.mipmap.ic_split_graph) + .into(h2.mImageOne); + mLoader.load(item.getImgs()[1] + FORMAT) + .fitCenter() + .into(h2.mImageTwo); + mLoader.load(item.getImgs()[2] + FORMAT) + .fitCenter() + .error(R.mipmap.ic_split_graph) + .into(h2.mImageThree); + if (mReadState.already(item.getKey())) { + h2.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + } else { + h2.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_title_color)); + } + break; + } + + } + + + private void setTag(TextView textView, ImageView imageView, Article article) { + if (article.getType() == Article.TYPE_QUESTION) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_question); + imageView.setVisibility(View.VISIBLE); + } else if (TypeFormat.isGit(article)) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_gitee); + imageView.setVisibility(View.VISIBLE); + } else if (article.getType() == Article.TYPE_ZB) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_zb); + imageView.setVisibility(View.VISIBLE); + } else if (article.getType() == Article.TYPE_SOFTWARE) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_software); + imageView.setVisibility(View.VISIBLE); + } else if (article.getType() == Article.TYPE_AD) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_ad); + imageView.setVisibility(View.VISIBLE); + } else if (article.getType() == Article.TYPE_TRANSLATE) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_translate); + imageView.setVisibility(View.VISIBLE); + } else { + textView.setText(article.getTitle()); + imageView.setVisibility(View.GONE); + } + } + + private void setEmptyTag(TextView textView, Article article) { + SpannableStringBuilder spannable = new SpannableStringBuilder(); + spannable.append("[icon] "); + spannable.append(article.getTitle()); + Drawable img = mContext.getResources().getDrawable(R.mipmap.tag_empty); + if (img != null) { + img.setBounds(0, 0, img.getIntrinsicWidth(), img.getIntrinsicHeight()); + } + ImageSpan imageSpan = new ImageSpan(img, ImageSpan.ALIGN_BOTTOM); + spannable.setSpan(imageSpan, 0, 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + textView.setText(spannable); + } + + + private static final class TextHolder extends RecyclerView.ViewHolder { + TextView mTextTitle, + mTextDesc, + mTextTime, + mTextOrigin, + mTextAuthor, + mTextCommentCount; + ImageView mImageTag; + + TextHolder(View itemView) { + super(itemView); + mImageTag = (ImageView) itemView.findViewById(R.id.iv_tag); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_title); + mTextDesc = (TextView) itemView.findViewById(R.id.tv_desc); + mTextTime = (TextView) itemView.findViewById(R.id.tv_time); + mTextOrigin = (TextView) itemView.findViewById(R.id.tv_origin); + mTextAuthor = (TextView) itemView.findViewById(R.id.tv_author); + mTextCommentCount = (TextView) itemView.findViewById(R.id.tv_comment_count); + } + } + + private static final class OneImgHolder extends RecyclerView.ViewHolder { + TextView mTextTitle, + mTextTime, + mTextOrigin, + mTextAuthor, + mTextCommentCount; + ImageView mImageView, mImageTag; + FrameLayout mFrameImage; + + OneImgHolder(View itemView) { + super(itemView); + mFrameImage = (FrameLayout) itemView.findViewById(R.id.fl_image); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_title); + mTextTime = (TextView) itemView.findViewById(R.id.tv_time); + mImageView = (ImageView) itemView.findViewById(R.id.iv_image); + mTextOrigin = (TextView) itemView.findViewById(R.id.tv_origin); + mTextAuthor = (TextView) itemView.findViewById(R.id.tv_author); + mImageTag = (ImageView) itemView.findViewById(R.id.iv_tag); + mTextCommentCount = (TextView) itemView.findViewById(R.id.tv_comment_count); + } + } + + private static final class ThreeImgHolder extends RecyclerView.ViewHolder { + TextView mTextTitle, + mTextTime, + mTextOrigin, + mTextAuthor, + mTextCommentCount; + ImageView mImageOne, mImageTwo, mImageThree, mImageTag; + + ThreeImgHolder(View itemView) { + super(itemView); + mImageTag = (ImageView) itemView.findViewById(R.id.iv_tag); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_title); + mTextTime = (TextView) itemView.findViewById(R.id.tv_time); + mImageOne = (ImageView) itemView.findViewById(R.id.iv_img_1); + mImageTwo = (ImageView) itemView.findViewById(R.id.iv_img_2); + mImageThree = (ImageView) itemView.findViewById(R.id.iv_img_3); + mTextOrigin = (TextView) itemView.findViewById(R.id.tv_origin); + mTextAuthor = (TextView) itemView.findViewById(R.id.tv_author); + mTextCommentCount = (TextView) itemView.findViewById(R.id.tv_comment_count); + } + } + + + private static final class HeaderHolder extends RecyclerView.ViewHolder { + HeaderHolder(View itemView) { + super(itemView); + } + } + + +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/article/ArticleContract.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/article/ArticleContract.java new file mode 100644 index 0000000000000000000000000000000000000000..a8f4248bdbd36bf8697f1edfa779c52b2be09f48 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/article/ArticleContract.java @@ -0,0 +1,24 @@ +package net.oschina.app.improve.main.synthesize.article; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.bean.Article; + +/** + * 头条界面 + * Created by huanghaibin on 2017/10/23. + */ + + interface ArticleContract { + + interface View extends BaseListView{ + /** + * 版本过期 + */ + void versionPast(); + } + + interface Presenter extends BaseListPresenter{ + void loadCache(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/article/ArticleFragment.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/article/ArticleFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..b54ea7ed0813571019e850ee4295b06a794106d2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/article/ArticleFragment.java @@ -0,0 +1,154 @@ +package net.oschina.app.improve.main.synthesize.article; + +import android.view.View; + +import net.oschina.app.OSCApplication; +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.main.banner.HeaderView; +import net.oschina.app.improve.main.banner.NewsHeaderView; +import net.oschina.app.improve.main.introduce.ArticleIntroduceActivity; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.main.synthesize.detail.ArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.english.detail.EnglishArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.web.WebActivity; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.interf.OnTabReselectListener; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; + +import java.util.List; + +/** + * 头条界面 + * Created by huanghaibin on 2017/10/23. + */ + +public class ArticleFragment extends BaseRecyclerFragment implements ArticleContract.View, OnTabReselectListener { + + private HeaderView mHeaderView; + private OSCApplication.ReadState mReadState; + + public static ArticleFragment newInstance() { + return new ArticleFragment(); + } + + @Override + protected void initWidget(View root) { + new ArticlePresenter(this); + super.initWidget(root); + } + + @Override + protected void initData() { + ArticleIntroduceActivity.show(mContext); + mReadState = OSCApplication.getReadState("sub_list"); + if (mPresenter != null) { + mPresenter.loadCache(); + } + mHeaderView = new NewsHeaderView(mContext, getImgLoader(), + "https://www.oschina.net/action/apiv2/banner?catalog=1", + "d6112fa662bc4bf21084670a857fbd20banner1"); + super.initData(); + mAdapter.setHeaderView(mHeaderView); + mRefreshLayout.setBottomCount(2); + } + + @Override + protected void onItemClick(Article top, int position) { + if (!TDevice.hasWebView(mContext)) + return; + if (top.getType() == 0) { + if (TypeFormat.isGit(top)) { + WebActivity.show(mContext, TypeFormat.formatUrl(top)); + } else { + ArticleDetailActivity.show(mContext, top); + } + } else { + int type = top.getType(); + long id = top.getOscId(); + switch (type) { + case News.TYPE_SOFTWARE: + SoftwareDetailActivity.show(mContext, id); + break; + case News.TYPE_QUESTION: + QuestionDetailActivity.show(mContext, id); + break; + case News.TYPE_BLOG: + BlogDetailActivity.show(mContext, id); + break; + case News.TYPE_TRANSLATE: + NewsDetailActivity.show(mContext, id, News.TYPE_TRANSLATE); + break; + case News.TYPE_EVENT: + EventDetailActivity.show(mContext, id); + break; + case News.TYPE_NEWS: + NewsDetailActivity.show(mContext, id); + break; + case Article.TYPE_ENGLISH: + EnglishArticleDetailActivity.show(mContext, top); + break; + default: + UIHelper.showUrlRedirect(mContext, top.getUrl()); + break; + } + } + mReadState.put(top.getKey()); + mAdapter.updateItem(position); + } + + + @Override + public void onRefreshing() { + super.onRefreshing(); + if (mHeaderView != null) { + mHeaderView.requestBanner(); + } + } + + @Override + public void onScrollToBottom() { + if (mPresenter != null) { + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + mPresenter.onLoadMore(); + } + } + + @Override + public void versionPast() { + if(mContext == null) + return; + SimplexToast.show(mContext,"当前版本已经过期,请升级最新版本"); + } + + @Override + public void onTabReselect() { + if (mRecyclerView != null && mPresenter != null) { + mRecyclerView.scrollToPosition(0); + mRefreshLayout.setRefreshing(true); + onRefreshing(); + } + } + + @Override + public void onRefreshSuccess(List
    data) { + super.onRefreshSuccess(data); + if (data.size() < 8) { + mRefreshLayout.setOnLoading(true); + onLoadMore(); + } + } + + @Override + protected BaseRecyclerAdapter
    getAdapter() { + return new ArticleAdapter(mContext, BaseRecyclerAdapter.BOTH_HEADER_FOOTER); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/article/ArticlePresenter.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/article/ArticlePresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..cf77692d1e05d7dd9b5348e03592cf51253cd167 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/article/ArticlePresenter.java @@ -0,0 +1,212 @@ +package net.oschina.app.improve.main.synthesize.article; + +import android.text.TextUtils; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.target.Target; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.Launcher; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.improve.utils.CacheManager; +import net.oschina.common.utils.CollectionUtil; +import net.oschina.common.utils.StreamUtil; + +import java.io.File; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; + +import cz.msebera.android.httpclient.Header; + +/** + * 头条界面 + * Created by huanghaibin on 2017/10/23. + */ + +class ArticlePresenter implements ArticleContract.Presenter { + private final ArticleContract.View mView; + private String mNextToken; + private static final String CACHE_NAME = "article_list"; + + ArticlePresenter(ArticleContract.View nView) { + this.mView = nView; + this.mView.setPresenter(this); + } + + @Override + public void loadCache() { + List
    items = CacheManager.readListJson(OSCApplication.getInstance(), CACHE_NAME, Article.class); + if (items != null) { + mView.onRefreshSuccess(items); + mView.onComplete(); + } + } + + @Override + public void onRefreshing() { + getLaunch(); + OSChinaApi.getArticles( + OSCSharedPreference.getInstance().getDeviceUUID(), + "", + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + try { + mView.showNotMore(); + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.onComplete(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null) { + if (bean.isSuccess()) { + PageBean
    pageBean = bean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List
    list = pageBean.getItems(); + for (Article article : list) { + article.setImgs(removeImgs(article.getImgs())); + } + CacheManager.saveToJson(OSCApplication.getInstance(), CACHE_NAME, list); + mView.onRefreshSuccess(list); + if (list.size() == 0) { + mView.showNotMore(); + } + } else if ("该版本不受支持,请下载最新的客户端".equals(bean.getMessage())) { + mView.versionPast(); + } else { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + OSChinaApi.getArticles( + OSCSharedPreference.getInstance().getDeviceUUID(), + mNextToken, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + PageBean
    pageBean = bean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List
    list = pageBean.getItems(); + for (Article article : list) { + article.setImgs(removeImgs(article.getImgs())); + } + mView.onLoadMoreSuccess(list); + if (list.size() == 0) { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + private static String[] removeImgs(String[] imgs) { + if (imgs == null || imgs.length == 0) + return null; + List list = new ArrayList<>(); + for (String img : imgs) { + if (!TextUtils.isEmpty(img)) { + if (img.startsWith("http")) { + list.add(img); + } + } + } + return CollectionUtil.toArray(list, String.class); + } + + private static void getLaunch() { + OSChinaApi.getLauncher(new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess() && bean.getResult() != null) { + CacheManager.saveToJson(OSCApplication.getInstance(), "Launcher.json", bean.getResult()); + saveAdImage(bean.getResult()); + }else { + CacheManager.removeCahche(OSCApplication.getInstance(), "Launcher.json"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + private static void saveAdImage(Launcher launcher) { + final Future future = Glide.with(OSCApplication.getInstance()) + .load(launcher.getImgUrl()) + .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL); + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + try { + File sourceFile = future.get(); + if (sourceFile == null || !sourceFile.exists()) + return; + String savePath = OSCApplication.getInstance().getCacheDir() + "/launcher"; + final File saveFile = new File(savePath); + StreamUtil.copyFile(sourceFile, saveFile); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/article/RatioLayout.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/article/RatioLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..2bd084c330ce28f8b850a282277a94410a2828bf --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/article/RatioLayout.java @@ -0,0 +1,56 @@ +package net.oschina.app.improve.main.synthesize.article; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +import net.oschina.app.R; + +/** + * 按缩放比显示的布局 + * Created by huanghaibin on 2017/11/14. + */ + +public class RatioLayout extends FrameLayout { + private float mRatio = 1.0f; + private int mFlag; + private static final int FLAG_WIDTH = 1; + private static final int FLAG_HEIGHT = 2; + private int mRatioHeight; + + public RatioLayout(@NonNull Context context) { + this(context, null); + } + + public RatioLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RatioLayout); + mRatio = array.getFloat(R.styleable.RatioLayout_layout_ratio, 1.0f); + mFlag = array.getInt(R.styleable.RatioLayout_ratio_flag, FLAG_WIDTH); + array.recycle(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width; + int height; + if (mFlag == FLAG_HEIGHT) { + height = MeasureSpec.getSize(heightMeasureSpec); + width = (int) ((float) height * mRatio); + widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); + } else { + width = MeasureSpec.getSize(widthMeasureSpec); + height = (int) ((float) width / mRatio); + mRatioHeight = height; + heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + public int getRatioHeight() { + return mRatioHeight; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/ArticleCommentActivity.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/ArticleCommentActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..fac07f090d24c366fe57f6eab5e87ce5d1b46b15 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/ArticleCommentActivity.java @@ -0,0 +1,239 @@ +package net.oschina.app.improve.main.synthesize.comment; + +import android.Manifest; +import android.content.DialogInterface; +import android.content.Intent; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.View; +import android.widget.LinearLayout; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.comment.CommentsActivity; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.CommentShareView; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.TDevice; + +import java.util.List; + +import butterknife.Bind; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * 头条评论列表 + * Created by huanghaibin on 2017/10/28. + */ + +public class ArticleCommentActivity extends BackActivity implements ArticleCommentContract.Action, + ArticleCommentFragment.OnCommentClickListener, + EasyPermissions.PermissionCallbacks { + + private String mMentionStr = ""; + protected CommentBar mDelegation; + private ArticleCommentPresenter mPresenter; + protected boolean mInputDoubleEmpty = false; + @Bind(R.id.shareView) + CommentShareView mShareView; + private AlertDialog mShareCommentDialog; + private Comment mComment; + protected long mCommentId; + protected long mCommentAuthorId; + private Article mArticle; + public static void show(AppCompatActivity activity, Article article) { + Intent intent = new Intent(activity, ArticleCommentActivity.class); + intent.putExtra("article", article); + activity.startActivityForResult(intent, 1); + } + + @Override + protected int getContentView() { + return R.layout.activity_article_comment; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mArticle = (Article) getIntent().getSerializableExtra("article"); + ArticleCommentFragment fragment = ArticleCommentFragment.newInstance(); + mPresenter = new ArticleCommentPresenter(fragment, this, mArticle); + addFragment(R.id.fl_content, fragment); + LinearLayout layComment = (LinearLayout) findViewById(R.id.ll_comment); + mDelegation = CommentBar.delegation(this, layComment); + mDelegation.hideFav(); + mDelegation.hideCommentCount(); + mDelegation.getBottomSheet().hideSyncAction(); + mDelegation.getBottomSheet().hideMentionAction(); + mDelegation.getBottomSheet().setMentionListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if ((AccountHelper.isLogin())) { + UserSelectFriendsActivity.show(ArticleCommentActivity.this, mDelegation.getBottomSheet().getEditText()); + } else { + LoginActivity.show(ArticleCommentActivity.this, 1); + } + } + }); + mDelegation.getBottomSheet().getEditText().setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL) { + handleKeyDel(); + } + return false; + } + }); + mDelegation.getBottomSheet().setCommitListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mPresenter.putArticleComment(mMentionStr + mDelegation.getBottomSheet().getCommentText(), mCommentId, mCommentAuthorId); + showLoadingDialog("正在发布评论..."); + mDelegation.getBottomSheet().dismiss(); + } + }); + + mShareCommentDialog = DialogHelper.getRecyclerViewDialog(this, new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + switch (position) { + case 0: + TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(mComment.getContent())); + break; + case 1: + if (!AccountHelper.isLogin()) { + LoginActivity.show(ArticleCommentActivity.this, 1); + return; + } + if (mComment.getAuthor() == null || + mComment.getAuthor().getId() == 0) { + SimplexToast.show(ArticleCommentActivity.this,"不能回复游客..."); + return; + } + mDelegation.getBottomSheet().getBtnCommit().setTag(mComment); + + mDelegation.getBottomSheet().show(String.format("%s %s", + getString(R.string.reply_hint), mComment.getAuthor().getName())); + break; + case 2: + mShareView.init(mArticle.getTitle(), mComment); + //mShareView.share(); + saveToFileByPermission(); + break; + } + mShareCommentDialog.dismiss(); + } + }).create(); + + } + + @Override + public void showAddCommentSuccess(Comment comment, int strId) { + if (isDestroy()) + return; + mCommentId = 0; + mCommentAuthorId = 0; + dismissLoadingDialog(); + mDelegation.getBottomSheet().getEditText().setText(""); + mDelegation.getBottomSheet().getEditText().setHint("发表评论"); + mMentionStr = ""; + mDelegation.getBottomSheet().dismiss(); + SimplexToast.show(this, strId); + } + + @Override + public void showAddCommentFailure(int strId) { + if (isDestroy()) + return; + dismissLoadingDialog(); + } + + @Override + public void onClick(Comment comment) { + mComment = comment; + mShareCommentDialog.show(); + Author author = comment.getAuthor(); + if (author == null) + return; + mCommentId = comment.getId(); + mCommentAuthorId = author.getId(); + } + + protected void handleKeyDel() { + if (!TextUtils.isEmpty(mMentionStr)) { + if (TextUtils.isEmpty(mDelegation.getBottomSheet().getCommentText())) { + if (mInputDoubleEmpty) { + mMentionStr = ""; + mDelegation.setCommentHint("发表评论"); + mDelegation.getBottomSheet().getEditText().setHint("发表评论"); + } else { + mInputDoubleEmpty = true; + } + } else { + mInputDoubleEmpty = false; + } + } + } + + @Override + public void finish() { + super.finish(); + setResult(RESULT_OK); + } + + private static final int PERMISSION_ID = 0x0001; + + @SuppressWarnings("unused") + @AfterPermissionGranted(PERMISSION_ID) + public void saveToFileByPermission() { + String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + if (EasyPermissions.hasPermissions(this, permissions)) { + mShareView.share(); + } else { + EasyPermissions.requestPermissions(this, "请授予文件读写权限", PERMISSION_ID, permissions); + } + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + DialogHelper.getConfirmDialog(this, "", "没有权限, 你需要去设置中开启读取手机存储权限.", "去设置", "取消", false, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + //finish(); + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //finish(); + } + }).show(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/ArticleCommentContract.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/ArticleCommentContract.java new file mode 100644 index 0000000000000000000000000000000000000000..ccb0ee910c2c835f308dfb9008b9713a339c1d65 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/ArticleCommentContract.java @@ -0,0 +1,29 @@ +package net.oschina.app.improve.main.synthesize.comment; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.bean.comment.Comment; + +/** + * 头条评论列表 + * Created by huanghaibin on 2017/10/28. + */ + +interface ArticleCommentContract { + + interface Action { + void showAddCommentSuccess(Comment comment, int strId); + + void showAddCommentFailure(int strId); + } + + interface View extends BaseListView { + void showAddCommentSuccess(Comment comment, int strId); + + void showAddCommentFailure(int strId); + } + + interface Presenter extends BaseListPresenter { + void putArticleComment(String content, long referId, long reAuthorId); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/ArticleCommentFragment.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/ArticleCommentFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..5a397f6e9b97db125f2aa3a056113fc750ba1f34 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/ArticleCommentFragment.java @@ -0,0 +1,61 @@ +package net.oschina.app.improve.main.synthesize.comment; + +import android.view.View; + +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.widget.SimplexToast; + +/** + * 头条评论 + * Created by huanghaibin on 2017/10/28. + */ + +public class ArticleCommentFragment extends BaseRecyclerFragment + implements ArticleCommentContract.View { + + private OnCommentClickListener mListener; + + public static ArticleCommentFragment newInstance() { + return new ArticleCommentFragment(); + } + + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mListener = (OnCommentClickListener) mContext; + } + + @Override + protected void onItemClick(Comment comment, int position) { + if (mListener != null) { + mListener.onClick(comment); + } + } + + + @Override + public void showAddCommentSuccess(Comment comment, int strId) { + if (mContext == null) + return; + mAdapter.addItem(comment); + } + + @Override + public void showAddCommentFailure(int strId) { + if (mContext == null) + return; + SimplexToast.show(mContext, strId); + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new CommentAdapter(mContext); + } + + public interface OnCommentClickListener { + void onClick(Comment comment); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/ArticleCommentPresenter.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/ArticleCommentPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..8c3baef387356a6a7dbcca41a816deaaa941cf1a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/ArticleCommentPresenter.java @@ -0,0 +1,160 @@ +package net.oschina.app.improve.main.synthesize.comment; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.comment.Comment; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * 头条评论列表 + * Created by huanghaibin on 2017/10/28. + */ +class ArticleCommentPresenter implements ArticleCommentContract.Presenter { + private final ArticleCommentContract.View mView; + private final ArticleCommentContract.Action mActionView; + private final Article mArticle; + private String mNextToken; + ArticleCommentPresenter(ArticleCommentContract.View mView, + ArticleCommentContract.Action mActionView, + Article article) { + this.mView = mView; + this.mArticle = article; + this.mActionView = mActionView; + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + OSChinaApi.getArticleComments(mArticle.getKey(), + 1, null, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + try { + mView.showNotMore(); + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + PageBean pageBean = bean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List list = pageBean.getItems(); + mView.onRefreshSuccess(list); + if (list.size() < 20) { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + OSChinaApi.getArticleComments(mArticle.getKey(), + 1, mNextToken, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + try { + mView.showNotMore(); + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + PageBean pageBean = bean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List list = pageBean.getItems(); + mView.onLoadMoreSuccess(list); + if (list.size() < 20) { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void putArticleComment(String content, long referId, long reAuthorId) { + OSChinaApi.pubArticleComment(mArticle.getKey(), + content, + referId, + reAuthorId, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.tip_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + Comment respComment = resultBean.getResult(); + if (respComment != null) { + mView.showAddCommentSuccess(respComment,R.string.pub_comment_success); + mActionView.showAddCommentSuccess(respComment,R.string.pub_comment_success); + } + } else { + mView.showAddCommentFailure(R.string.pub_comment_failed); + mActionView.showAddCommentFailure(R.string.pub_comment_failed); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + mView.showAddCommentFailure(R.string.pub_comment_failed); + mActionView.showAddCommentFailure(R.string.pub_comment_failed); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/CommentAdapter.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/CommentAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..2bc4dfbfee1660899cab8c84dc7095deaa62cfc7 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/comment/CommentAdapter.java @@ -0,0 +1,254 @@ +package net.oschina.app.improve.main.synthesize.comment; + +import android.annotation.SuppressLint; +import android.app.ProgressDialog; +import android.content.Context; +import android.support.annotation.StringRes; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.comment.Vote; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.comment.CommentReferView; +import net.oschina.app.improve.comment.CommentsUtil; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; +import net.oschina.app.widget.TweetTextView; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import butterknife.ButterKnife; +import cz.msebera.android.httpclient.Header; + +/** + * 热门评论 + * Created by huanghaibin on 2017/10/28. + */ + +public class CommentAdapter extends BaseRecyclerAdapter { + public CommentAdapter(Context context) { + super(context, ONLY_FOOTER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new Holder(mInflater.inflate(R.layout.item_list_article_comment, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Comment item, int position) { + if (holder instanceof Holder) { + ((Holder) holder).addComment(item); + } + } + + static final class Holder extends RecyclerView.ViewHolder { + + private ProgressDialog mDialog; + + @Bind(R.id.iv_avatar) + PortraitView mIvAvatar; + + @Bind(R.id.identityView) + IdentityView mIdentityView; + + @Bind(R.id.tv_name) + TextView mName; + @Bind(R.id.tv_pub_date) + TextView mPubDate; + @Bind(R.id.tv_vote_count) + TextView mVoteCount; + @Bind(R.id.btn_vote) + ImageView mVote; + + @Bind(R.id.lay_refer) + CommentReferView mCommentReferView; + + @Bind(R.id.tv_content) + TweetTextView mTweetTextView; + @Bind(R.id.line) + View mLine; + + Holder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + + /** + * add comment + * + * @param comment comment + */ + @SuppressLint("DefaultLocale") + void addComment(final Comment comment) { + mIdentityView.setup(comment.getAuthor()); + mIvAvatar.setup(comment.getAuthor()); + mIvAvatar.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + OtherUserHomeActivity.show(mIvAvatar.getContext(), comment.getAuthor().getId()); + } + }); + Author author = comment.getAuthor(); + String name; + if (author == null || TextUtils.isEmpty(name = author.getName())) + name = mName.getResources().getString(R.string.martian_hint); + mName.setText(name); + mPubDate.setText(String.format("%s", StringUtils.formatSomeAgo(comment.getPubDate()))); + + + mVoteCount.setText(String.valueOf(comment.getVote())); + mVoteCount.setVisibility(View.VISIBLE); + mVote.setVisibility(View.VISIBLE); + if (comment.getVoteState() == 1) { + mVote.setImageResource(R.mipmap.ic_thumbup_actived); + mVote.setTag(true); + } else if (comment.getVoteState() == 0) { + mVote.setImageResource(R.mipmap.ic_thumb_normal); + mVote.setTag(null); + } + mVote.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + handVote(); + } + + private void handVote() { + if (!AccountHelper.isLogin()) { + LoginActivity.show(mVote.getContext()); + return; + } + if (!TDevice.hasInternet()) { + AppContext.showToast(mVote.getResources().getString(R.string.state_network_error), Toast.LENGTH_SHORT); + return; + } + OSChinaApi.voteArticleComment(comment.getId(), comment.getAuthor().getId(), new TextHttpResponseHandler() { + + @Override + public void onStart() { + super.onStart(); + showWaitDialog(R.string.progress_submit); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + requestFailureHint(throwable); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, getVoteType()); + if (resultBean.isSuccess()) { + Vote vote = resultBean.getResult(); + if (vote != null) { + if (vote.getVoteState() == 1) { + comment.setVoteState(1); + mVote.setTag(true); + mVote.setImageResource(R.mipmap.ic_thumbup_actived); + } else if (vote.getVoteState() == 0) { + comment.setVoteState(0); + mVote.setTag(null); + mVote.setImageResource(R.mipmap.ic_thumb_normal); + } + long voteVoteCount = vote.getVote(); + comment.setVote(voteVoteCount); + mVoteCount.setText(String.valueOf(voteVoteCount)); + } + } else { + AppContext.showToast(resultBean.getMessage(), Toast.LENGTH_SHORT); + } + } + + @Override + public void onFinish() { + super.onFinish(); + hideWaitDialog(); + } + }); + } + }); + + mCommentReferView.addComment(comment); + + CommentsUtil.formatHtml(mTweetTextView.getResources(), mTweetTextView, comment.getContent()); + } + + /** + * show WaitDialog + * + * @return progressDialog + */ + private ProgressDialog showWaitDialog(@StringRes int messageId) { + + if (mDialog == null) { + if (messageId <= 0) { + mDialog = DialogHelper.getProgressDialog(mVote.getContext(), true); + } else { + String message = mVote.getContext().getResources().getString(messageId); + mDialog = DialogHelper.getProgressDialog(mVote.getContext(), message, true); + } + } + mDialog.show(); + + return mDialog; + } + + /** + * hide waitDialog + */ + private void hideWaitDialog() { + ProgressDialog dialog = mDialog; + if (dialog != null) { + mDialog = null; + try { + dialog.cancel(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + /** + * request network error + * + * @param throwable throwable + */ + private void requestFailureHint(Throwable throwable) { + AppContext.showToast(R.string.request_error_hint); + if (throwable != null) { + throwable.printStackTrace(); + } + } + + /** + * @return TypeToken + */ + Type getVoteType() { + return new TypeToken>() { + }.getType(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/ArticleDetailActivity.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/ArticleDetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..9c769ce90252924c65d075327de0ea3fa90bf8e9 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/ArticleDetailActivity.java @@ -0,0 +1,523 @@ +package net.oschina.app.improve.main.synthesize.detail; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Handler; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.widget.LinearLayout; + +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.bean.Report; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.detail.db.Behavior; +import net.oschina.app.improve.detail.db.DBManager; +import net.oschina.app.improve.detail.v2.ReportDialog; +import net.oschina.app.improve.main.ClipManager; +import net.oschina.app.improve.main.synthesize.comment.ArticleCommentActivity; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.improve.media.Util; +import net.oschina.app.improve.share.ShareDialog; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.utils.NetworkUtil; +import net.oschina.app.improve.widget.CommentShareView; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.improve.widget.adapter.OnKeyArrivedListenerAdapterV2; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.TDevice; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * 头条详情 + * Created by huanghaibin on 2017/10/23. + */ + +public class ArticleDetailActivity extends BackActivity implements + CommentView.OnCommentClickListener, + ArticleDetailContract.EmptyView, + EasyPermissions.PermissionCallbacks { + private CommentBar mDelegation; + private String mCommentHint; + private Article mArticle; + protected long mCommentId; + protected long mCommentAuthorId; + protected boolean mInputDoubleEmpty = false; + private ArticleDetailPresenter mPresenter; + protected EmptyLayout mEmptyLayout; + protected CommentShareView mShareView; + private AlertDialog mShareCommentDialog; + protected ShareDialog mShareDialog; + protected Comment mComment; + protected Behavior mBehavior; + private long mStart; + protected long mStay;//该界面停留时间 + private ArticleDetailFragment mFragment; + + public static void show(Context context, Article article) { + if (article == null) + return; + Intent intent = new Intent(context, ArticleDetailActivity.class); + intent.putExtra("article", article); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_article_detail; + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mArticle = (Article) getIntent().getSerializableExtra("article"); + mFragment = ArticleDetailFragment.newInstance(mArticle); + mPresenter = new ArticleDetailPresenter(mFragment, this, mArticle); + addFragment(R.id.fl_content, mFragment); + + LinearLayout layComment = (LinearLayout) findViewById(R.id.ll_comment); + if (TextUtils.isEmpty(mCommentHint)) + mCommentHint = getString(R.string.pub_comment_hint); + mDelegation = CommentBar.delegation(this, layComment); + mDelegation.setCommentHint(mCommentHint); + mDelegation.getBottomSheet().getEditText().setHint(mCommentHint); + //mDelegation.setFavDrawable(mBean.isFavorite() ? R.drawable.ic_faved : R.drawable.ic_fav); + mShareView = (CommentShareView) findViewById(R.id.shareView); + mEmptyLayout = (EmptyLayout) findViewById(R.id.lay_error); + mEmptyLayout.setOnLayoutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mEmptyLayout.getErrorState() != EmptyLayout.NETWORK_LOADING) { + mEmptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + //mPresenter.getDetail(); + } + } + }); + + mDelegation.setFavListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if ((AccountHelper.isLogin())) { + mPresenter.fav(); + } else { + LoginActivity.show(ArticleDetailActivity.this, 1); + } + } + }); + + mDelegation.getBottomSheet().setMentionListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if ((AccountHelper.isLogin())) { + UserSelectFriendsActivity.show(ArticleDetailActivity.this, mDelegation.getBottomSheet().getEditText()); + } else { + LoginActivity.show(ArticleDetailActivity.this, 1); + } + } + }); + + + mDelegation.setCommentCountListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ArticleCommentActivity.show(ArticleDetailActivity.this, mArticle); + } + }); + + mDelegation.getBottomSheet().getEditText().setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL) { + handleKeyDel(); + } + return false; + } + }); + mDelegation.getBottomSheet().hideSyncAction(); + mDelegation.getBottomSheet().setCommitListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showLoadingDialog("正在提交评论..."); + if (mDelegation == null) return; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(false); + mPresenter.putArticleComment(mDelegation.getBottomSheet().getCommentText(), + mCommentId, + mCommentAuthorId + ); + } + }); + mDelegation.getBottomSheet().getEditText().setOnKeyArrivedListener(new OnKeyArrivedListenerAdapterV2(this)); + + if (mShareView != null) { + mShareCommentDialog = DialogHelper.getRecyclerViewDialog(this, new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + switch (position) { + case 0: + TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(mComment.getContent())); + break; + case 1: + if (!AccountHelper.isLogin()) { + LoginActivity.show(ArticleDetailActivity.this, 1); + return; + } + if (mComment.getAuthor() == null || mComment.getAuthor().getId() == 0) { + SimplexToast.show(ArticleDetailActivity.this, "该用户不存在"); + return; + } + mCommentId = mComment.getId(); + mCommentAuthorId = mComment.getAuthor().getId(); + mDelegation.getCommentText().setHint(String.format("%s %s", getResources().getString(R.string.reply_hint), mComment.getAuthor().getName())); + mDelegation.getBottomSheet().show(String.format("%s %s", getResources().getString(R.string.reply_hint), mComment.getAuthor().getName())); + break; + case 2: + mShareView.init(mArticle.getTitle(), mComment); + saveToFileByPermission(); + break; + } + mShareCommentDialog.dismiss(); + } + }).create(); + } + mDelegation.setCommentCount(mArticle.getCommentCount()); + + mShareDialog = new ShareDialog(this); + mShareDialog.setTitle(mArticle.getTitle()); + mShareDialog.init(this, mArticle.getTitle(), mArticle.getDesc(), mArticle.getUrl()); + if (mArticle.getImgs() != null && mArticle.getImgs().length != 0) { + getImageLoader().load(mArticle.getImgs()[0]) + .asBitmap() + .centerCrop() + .listener(new RequestListener() { + @Override + public boolean onException(Exception e, String model, Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(Bitmap resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { + if (isDestroy()) + return false; + mShareDialog.setThumbBitmap(resource); + return false; + } + }) + .into(Util.dipTopx(this, 80), + Util.dipTopx(this, 80)); + } + } + + @SuppressLint("ClickableViewAccessibility") + @SuppressWarnings("all") + @Override + protected void initData() { + super.initData(); + if (AccountHelper.isLogin() && + DBManager.getInstance() + .getCount(Behavior.class) >= 3) { + mPresenter.uploadBehaviors(DBManager.getInstance().get(Behavior.class)); + } + mBehavior = new Behavior(); + mBehavior.setUser(AccountHelper.getUserId()); + mBehavior.setUserName(AccountHelper.getUser().getName()); + mBehavior.setNetwork(NetworkUtil.getNetwork(this)); + mBehavior.setUrl(mArticle.getUrl()); + mBehavior.setOperateType(mArticle.getType()); + mBehavior.setOperateTime(System.currentTimeMillis()); + mStart = mBehavior.getOperateTime(); + mBehavior.setOperation("read"); + mBehavior.setDevice(android.os.Build.MODEL); + mBehavior.setVersion(TDevice.getVersionName()); + mBehavior.setOs(android.os.Build.VERSION.RELEASE); + mBehavior.setKey(mArticle.getKey()); + mBehavior.setUuid(OSCSharedPreference.getInstance().getDeviceUUID()); + // TODO: 2017/11/6 暂时取消收藏习惯接口 + //DBManager.getInstance().insert(mBehavior); + + + mToolBar.setOnTouchListener(new OnDoubleTouchListener() { + @Override + void onMultiTouch(View v, MotionEvent event, int touchCount) { + if (touchCount == 2) { + mPresenter.scrollToTop(); + } + } + }); + } + + protected void handleKeyDel() { + if (mCommentId != 0) { + if (TextUtils.isEmpty(mDelegation.getBottomSheet().getCommentText())) { + if (mInputDoubleEmpty) { + mCommentId = 0; + mCommentAuthorId = 0; + mDelegation.setCommentHint(mCommentHint); + mDelegation.getBottomSheet().getEditText().setHint(mCommentHint); + } else { + mInputDoubleEmpty = true; + } + } else { + mInputDoubleEmpty = false; + } + } + } + + @Override + public void onClick(View view, Comment comment) { + this.mComment = comment; + if (mShareCommentDialog != null) { + mShareCommentDialog.show(); + } + } + + @Override + public void showFavReverseSuccess(boolean isFav) { + if (isDestroyed()) { + return; + } + mDelegation.setFavDrawable(isFav ? R.drawable.ic_faved : R.drawable.ic_fav); + } + + @Override + public void showFavError() { + + } + + @Override + public void onShowComment(View view) { + if (mDelegation != null) { + mDelegation.getBottomSheet().show(mCommentHint); + } + } + + @Override + public void showCommentSuccess(Comment comment) { + if (isDestroyed()) + return; + if (mDelegation == null) + return; + mCommentId = 0; + mCommentAuthorId = 0; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(true); + AppContext.showToastShort(R.string.pub_comment_success); + mDelegation.getCommentText().setHint(mCommentHint); + mDelegation.getBottomSheet().getEditText().setText(""); + mDelegation.getBottomSheet().getEditText().setHint(mCommentHint); + mDelegation.getBottomSheet().dismiss(); + dismissLoadingDialog(); + SimplexToast.show(this, "评论成功"); + mDelegation.setCommentCount(mArticle.getCommentCount()); + } + + @Override + public void showCommentError(String message) { + if (isDestroy()) + return; + dismissLoadingDialog(); + SimplexToast.show(this, "评论失败"); + } + + @Override + public void showGetDetailSuccess(Article article) { + if (isDestroy()) + return; + mEmptyLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + mDelegation.setFavDrawable(article.isFavorite() ? R.drawable.ic_faved : R.drawable.ic_fav); + mDelegation.setCommentCount(article.getCommentCount()); + } + + @Override + public void showErrorLayout(int errorType) { + if (isDestroy()) { + return; + } + mEmptyLayout.setErrorType(errorType); + } + + @SuppressLint("SetTextI18n") + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_blog_detail, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + if (mArticle != null) { + ClipManager.IS_SYSTEM_URL = true; + mShareDialog.show(); + } + break; + case R.id.menu_report: + if (!AccountHelper.isLogin()) { + LoginActivity.show(this); + return false; + } + if (mArticle != null) { + ReportDialog.create(this, 0, mArticle.getUrl(), Report.TYPE_ARTICLE, mArticle.getKey()).show(); + } + break; + } + return false; + } + + + @Override + protected void onResume() { + super.onResume(); + if (mStart != 0) + mStart = System.currentTimeMillis(); + } + + @Override + protected void onStop() { + super.onStop(); + ClipManager.IS_SYSTEM_URL = false; + mStay += (System.currentTimeMillis() - mStart) / 1000; + if (mBehavior != null) { + mBehavior.setStay(mStay); + DBManager.getInstance() + .update(mBehavior, "operate_time=?", String.valueOf(mBehavior.getOperateTime())); + } + } + + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK && mFragment != null) { + mFragment.onRefreshing(); + } + } + + private static final int PERMISSION_ID = 0x0001; + + @SuppressWarnings("unused") + @AfterPermissionGranted(PERMISSION_ID) + public void saveToFileByPermission() { + String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + if (EasyPermissions.hasPermissions(this, permissions)) { + mShareView.share(); + } else { + EasyPermissions.requestPermissions(this, "请授予文件读写权限", PERMISSION_ID, permissions); + } + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + DialogHelper.getConfirmDialog(this, "", "没有权限, 你需要去设置中开启读取手机存储权限.", "去设置", "取消", false, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + //finish(); + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //finish(); + } + }).show(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + + @Override + protected void onDestroy() { + super.onDestroy(); + ClipManager.IS_SYSTEM_URL = false; + } + + private abstract class OnDoubleTouchListener implements View.OnTouchListener { + private long lastTouchTime = 0; + private AtomicInteger touchCount = new AtomicInteger(0); + private Runnable mRun = null; + private Handler mHandler; + + OnDoubleTouchListener() { + mHandler = new Handler(getMainLooper()); + } + + void removeCallback() { + if (mRun != null) { + mHandler.removeCallbacks(mRun); + mRun = null; + } + } + + @Override + public boolean onTouch(final View v, final MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + final long now = System.currentTimeMillis(); + lastTouchTime = now; + + touchCount.incrementAndGet(); + removeCallback(); + + mRun = new Runnable() { + @Override + public void run() { + if (now == lastTouchTime) { + onMultiTouch(v, event, touchCount.get()); + touchCount.set(0); + } + } + }; + + mHandler.postDelayed(mRun, getMultiTouchInterval()); + } + return true; + } + + + int getMultiTouchInterval() { + return 400; + } + + + abstract void onMultiTouch(View v, MotionEvent event, int touchCount); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/ArticleDetailContract.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/ArticleDetailContract.java new file mode 100644 index 0000000000000000000000000000000000000000..0398fa021c80144c519424a022fd6ddbc1455ed9 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/ArticleDetailContract.java @@ -0,0 +1,63 @@ +package net.oschina.app.improve.main.synthesize.detail; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.detail.db.Behavior; + +import java.util.List; + +/** + * 头条详情 + * Created by huanghaibin on 2017/10/23. + */ + +interface ArticleDetailContract { + + interface EmptyView { + + void showCommentSuccess(Comment comment); + + void showCommentError(String message); + + void showFavReverseSuccess(boolean isFav); + + void showFavError(); + + void showErrorLayout(int errorType); + + void showGetDetailSuccess(Article article); + } + + interface View extends BaseListView { + void showCommentSuccess(Comment comment); + + void showCommentError(String message); + + void showGetDetailSuccess(Article article); + + void showScrollToTop(); + } + + interface Presenter extends BaseListPresenter { + + void getArticleDetail(); + + void putArticleComment(String content, long referId, long reAuthorId); + + void uploadBehaviors(List behaviors); + + void addClickCount(); + + void fav(); + + void scrollToTop(); + + String formatTextCount(int count); + + String formatTime(long time); + + String formatTimeUnit(long time); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/ArticleDetailFragment.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/ArticleDetailFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..f88257029d65c871167b1bfb72a091b1bcf1fa93 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/ArticleDetailFragment.java @@ -0,0 +1,354 @@ +package net.oschina.app.improve.main.synthesize.detail; + +import android.annotation.SuppressLint; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.CheckBox; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; +import com.zhy.view.flowlayout.FlowLayout; +import com.zhy.view.flowlayout.TagAdapter; +import com.zhy.view.flowlayout.TagFlowLayout; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.Tag; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.main.synthesize.DataFormat; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.main.synthesize.article.ArticleAdapter; +import net.oschina.app.improve.main.synthesize.english.detail.EnglishArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.web.ArticleWebActivity; +import net.oschina.app.improve.main.synthesize.web.WebActivity; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.improve.media.ImageGalleryActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.UIHelper; + +import java.util.List; + +/** + * 文章详情 + * Created by huanghaibin on 2017/10/27. + */ + +public class ArticleDetailFragment extends BaseRecyclerFragment + implements ArticleDetailContract.View, + View.OnClickListener { + private OSCApplication.ReadState mReadState; + protected CommentView mCommentView; + private Article mArticle; + private View mHeaderView; + private ProgressBar mLoadingBar; + private TagFlowLayout mFlowLayout; + private TextView mTextCount; + private TextView mTextTime; + private TextView mTextTimeUnit; + private LinearLayout mLinearCount; + + public static ArticleDetailFragment newInstance(Article article) { + Bundle bundle = new Bundle(); + bundle.putSerializable("article", article); + ArticleDetailFragment fragment = new ArticleDetailFragment(); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_article; + } + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + mArticle = (Article) bundle.getSerializable("article"); + } + + @SuppressLint("InflateParams,CutPasteId") + @Override + protected void initData() { + mReadState = OSCApplication.getReadState("sub_list"); + mHeaderView = mInflater.inflate(R.layout.layout_article_header, null); + mAdapter.setHeaderView(mHeaderView); + ImageView imageView = (ImageView) mHeaderView.findViewById(R.id.iv_article); + FrameLayout frameLayout = (FrameLayout) mHeaderView.findViewById(R.id.fl_img); + mLoadingBar = (ProgressBar) mHeaderView.findViewById(R.id.pb_loading); + if (mArticle.getImgs() != null && mArticle.getImgs().length != 0) { + imageView.setVisibility(View.VISIBLE); + frameLayout.setVisibility(View.VISIBLE); + getImgLoader().load(mArticle.getImgs()[0]) + .centerCrop() + .listener(new RequestListener() { + @Override + public boolean onException(Exception e, String model, Target target, boolean isFirstResource) { + mLoadingBar.setVisibility(View.GONE); + return false; + } + + @Override + public boolean onResourceReady(GlideDrawable resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { + mLoadingBar.setVisibility(View.GONE); + return false; + } + }) + .into(imageView); + imageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ImageGalleryActivity.show(mContext, mArticle.getImgs()[0]); + } + }); + } + mLinearCount = (LinearLayout) mHeaderView.findViewById(R.id.ll_count); + mFlowLayout = (TagFlowLayout) mHeaderView.findViewById(R.id.flowLayout); + mTextCount = (TextView) mHeaderView.findViewById(R.id.tv_text_count); + mTextTime = (TextView) mHeaderView.findViewById(R.id.tv_text_time); + mTextTimeUnit = (TextView) mHeaderView.findViewById(R.id.tv_text_time_unit); + TextView tv_title = (TextView) mHeaderView.findViewById(R.id.tv_title); + TextView tv_name = (TextView) mHeaderView.findViewById(R.id.tv_name); + TextView tv_pub_date = (TextView) mHeaderView.findViewById(R.id.tv_pub_date); + TextView tv_origin = (TextView) mHeaderView.findViewById(R.id.tv_origin); + TextView tv_detail_abstract = (TextView) mHeaderView.findViewById(R.id.tv_detail_abstract); + tv_title.setText(mArticle.getTitle()); + tv_name.setText(TextUtils.isEmpty(mArticle.getAuthorName()) ? "匿名" : mArticle.getAuthorName()); + tv_pub_date.setText(DataFormat.parsePubDate(mArticle.getPubDate())); + tv_detail_abstract.setText(TextUtils.isEmpty(mArticle.getDesc()) ? mArticle.getDesc() : mArticle.getDesc().replaceFirst("\\s*|\t|\n", "")); + //tv_detail_abstract.setText(TextUtils.isEmpty(mArticle.getDesc()) ? mArticle.getDesc() : mArticle.getDesc().trim()); + PortraitView portraitView = (PortraitView) mHeaderView.findViewById(R.id.iv_avatar); + tv_origin.setText(mArticle.getSource()); + if (TextUtils.isEmpty(mArticle.getSource())) { + tv_origin.setVisibility(View.GONE); + } + Author author = new Author(); + author.setName(mArticle.getAuthorName()); + portraitView.setup(author); + mCommentView = (CommentView) mHeaderView.findViewById(R.id.commentView); + mCommentView.setTitle("热门评论"); + mCommentView.init(mArticle, mArticle.getKey(), 2, (CommentView.OnCommentClickListener) mContext); + mHeaderView.findViewById(R.id.btn_read_all).setOnClickListener(this); + mRefreshLayout.post(new Runnable() { + @Override + public void run() { + mRefreshLayout.setRefreshing(true); + mRefreshLayout.setOnLoading(true); + if (mPresenter == null) + return; + mPresenter.getArticleDetail(); + mPresenter.onRefreshing(); + } + }); + } + + @Override + public void onRefreshing() { + super.onRefreshing(); + if (mCommentView != null) + mCommentView.init(mArticle, mArticle.getKey(), 2, (CommentView.OnCommentClickListener) mContext); + } + + @Override + public void onScrollToBottom() { + if (mPresenter != null) { + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + mPresenter.onLoadMore(); + } + } + + @SuppressLint("InflateParams") + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_read_all: + if (OSCSharedPreference.getInstance().isFirstOpenUrl()) { + View view = mInflater.inflate(R.layout.dialog_liability, null); + final CheckBox checkBox = (CheckBox) view.findViewById(R.id.cb_url); + DialogHelper.getConfirmDialog(mContext, + "温馨提醒", + view, + "继续访问", + "取消", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ArticleWebActivity.show(mContext, mArticle); + mPresenter.addClickCount(); + if (checkBox.isChecked()) { + OSCSharedPreference.getInstance().putFirstOpenUrl(); + } + } + }).show(); + } else { + ArticleWebActivity.show(mContext, mArticle); + mPresenter.addClickCount(); + } + break; + } + } + + @Override + protected void onItemClick(Article top, int position) { + if (top.getType() == 0) { + if (TypeFormat.isGit(top)) { + WebActivity.show(mContext, TypeFormat.formatUrl(top)); + } else { + ArticleDetailActivity.show(mContext, top); + } + } else { + try { + int type = top.getType(); + long id = top.getOscId(); + switch (type) { + case News.TYPE_SOFTWARE: + SoftwareDetailActivity.show(mContext, id); + break; + case News.TYPE_QUESTION: + QuestionDetailActivity.show(mContext, id); + break; + case News.TYPE_BLOG: + BlogDetailActivity.show(mContext, id); + break; + case News.TYPE_TRANSLATE: + NewsDetailActivity.show(mContext, id, News.TYPE_TRANSLATE); + break; + case News.TYPE_EVENT: + EventDetailActivity.show(mContext, id); + break; + case News.TYPE_NEWS: + NewsDetailActivity.show(mContext, id); + break; + case Article.TYPE_ENGLISH: + EnglishArticleDetailActivity.show(mContext, top); + break; + default: + UIHelper.showUrlRedirect(mContext, top.getUrl()); + break; + } + + } catch (Exception e) { + e.printStackTrace(); + ArticleDetailActivity.show(mContext, top); + } + } + mReadState.put(top.getKey()); + mAdapter.updateItem(position); + } + + @Override + public void showCommentSuccess(Comment comment) { + + } + + @Override + public void showCommentError(String message) { + + } + + @Override + public void onRefreshSuccess(List
    data) { + super.onRefreshSuccess(data); + mRefreshLayout.setCanLoadMore(true); + } + + @Override + public void onLoadMoreSuccess(List
    data) { + super.onLoadMoreSuccess(data); + if (data != null && data.size() > 0) { + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + } else { + mRefreshLayout.setCanLoadMore(false); + } + } + + @SuppressWarnings("all") + @Override + public void showGetDetailSuccess(final Article article) { + if (mContext == null) + return; + mLinearCount.setVisibility(article.getWordCount() != 0 ? View.VISIBLE : View.GONE); + mTextCount.setText(mPresenter.formatTextCount(article.getWordCount())); + mTextTime.setText(mPresenter.formatTime(article.getReadTime())); + mTextTimeUnit.setText(mPresenter.formatTimeUnit(article.getReadTime())); + mCommentView.init(mArticle, mArticle.getKey(), 2, (CommentView.OnCommentClickListener) mContext); + mCommentView.setCommentCount(article); + mFlowLayout.removeAllViews(); + if (article.getiTags() == null || article.getiTags().length == 0) { + mFlowLayout.setVisibility(View.GONE); + return; + } + mFlowLayout.setVisibility(View.VISIBLE); + mFlowLayout.setAdapter(new TagAdapter(article.getiTags()) { + @Override + public View getView(FlowLayout parent, int position, final Tag tag) { + TextView tvTag = (TextView) getActivity().getLayoutInflater().inflate(R.layout.tag_item, mFlowLayout, false); + tvTag.setText(tag.getName()); + + return tvTag; + } + }); + mFlowLayout.setOnTagClickListener(new TagFlowLayout.OnTagClickListener() { + @Override + public boolean onTagClick(View view, int position, FlowLayout parent) { + Tag tag = article.getiTags()[position]; + if (tag.getOscId() != 0) { + SoftwareDetailActivity.show(mContext, tag.getOscId()); + } + return true; + } + }); + } + + @Override + public void showScrollToTop() { + if (mRecyclerView != null) { + mRecyclerView.scrollToPosition(0); + } + } + + + @Override + public void onComplete() { + super.onComplete(); + if (mContext == null) + return; + hideOrShowTitle(mAdapter.getItems().size() != 0); + } + + private void hideOrShowTitle(boolean isShow) { + if (isShow) { + mHeaderView.findViewById(R.id.line1).setVisibility(View.VISIBLE); + mHeaderView.findViewById(R.id.line2).setVisibility(View.VISIBLE); + mHeaderView.findViewById(R.id.tv_recommend).setVisibility(View.VISIBLE); + } else { + mHeaderView.findViewById(R.id.line1).setVisibility(View.GONE); + mHeaderView.findViewById(R.id.line2).setVisibility(View.GONE); + mHeaderView.findViewById(R.id.tv_recommend).setVisibility(View.GONE); + mAdapter.setState(BaseRecyclerAdapter.STATE_HIDE, true); + } + } + + @Override + protected BaseRecyclerAdapter
    getAdapter() { + return new ArticleAdapter(mContext, BaseRecyclerAdapter.BOTH_HEADER_FOOTER); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/ArticleDetailPresenter.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/ArticleDetailPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..63c545b2e3a65259632cc08d604e0aaead0186dd --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/ArticleDetailPresenter.java @@ -0,0 +1,345 @@ +package net.oschina.app.improve.main.synthesize.detail; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppConfig; +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.Collection; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.detail.db.API; +import net.oschina.app.improve.detail.db.Behavior; +import net.oschina.app.improve.detail.db.DBManager; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.common.utils.CollectionUtil; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * 头条详情 + * Created by huanghaibin on 2017/10/23. + */ + +class ArticleDetailPresenter implements ArticleDetailContract.Presenter { + private final ArticleDetailContract.View mView; + private final ArticleDetailContract.EmptyView mEmptyView; + private String mNextToken; + private Article mArticle; + + ArticleDetailPresenter(ArticleDetailContract.View mView, ArticleDetailContract.EmptyView mEmptyView, Article article) { + this.mView = mView; + this.mArticle = article; + this.mEmptyView = mEmptyView; + this.mView.setPresenter(this); + } + + + @Override + public void getArticleDetail() { + OSChinaApi.getArticleDetail(mArticle.getKey(), + OSCSharedPreference.getInstance().getDeviceUUID(), + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mEmptyView.showErrorLayout(EmptyLayout.NETWORK_ERROR); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean
    bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess() && bean.getResult() != null) { + mArticle = bean.getResult(); + mView.showGetDetailSuccess(mArticle); + mEmptyView.showGetDetailSuccess(mArticle); + } else { + mEmptyView.showErrorLayout(EmptyLayout.NODATA); + } + } catch (Exception e) { + e.printStackTrace(); + mEmptyView.showErrorLayout(EmptyLayout.NODATA); + } + } + }); + } + + @Override + public void addClickCount() { + OSChinaApi.addClickCount(mArticle.getKey(), new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + } + }); + } + + @Override + public void scrollToTop() { + mView.showScrollToTop(); + } + + @Override + public void fav() { + OSChinaApi.articleFav(new Gson().toJson(mArticle), + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mEmptyView.showFavError(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean != null && resultBean.isSuccess()) { + Collection collection = resultBean.getResult(); + mArticle.setFavorite(collection.isFavorite()); + mEmptyView.showFavReverseSuccess(collection.isFavorite()); + } else { + mEmptyView.showFavError(); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + }); + } + + @Override + public void onRefreshing() { + OSChinaApi.getArticleRecommends( + mArticle.getKey(), + OSCSharedPreference.getInstance().getDeviceUUID(), + "", + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + try { + mView.showNotMore(); + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + PageBean
    pageBean = bean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List
    list = pageBean.getItems(); + for (Article article : list) { + article.setImgs(removeImgs(article.getImgs())); + } + mView.onRefreshSuccess(list); + if (list.size() == 0) { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + OSChinaApi.getArticleRecommends( + mArticle.getKey(), + OSCSharedPreference.getInstance().getDeviceUUID(), + mNextToken, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + try { + mView.showNotMore(); + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + PageBean
    pageBean = bean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List
    list = pageBean.getItems(); + for (Article article : list) { + article.setImgs(removeImgs(article.getImgs())); + } + mView.onLoadMoreSuccess(list); + if (list.size() == 0) { + mView.showNotMore(); + } + } else { + mView.showNetworkError(R.string.footer_type_net_error); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void putArticleComment(String content, long referId, long reAuthorId) { + OSChinaApi.pubArticleComment(mArticle.getKey(), + content, + referId, + reAuthorId, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.tip_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + mArticle.setCommentCount(mArticle.getCommentCount() + 1); + Comment respComment = resultBean.getResult(); + if (respComment != null) { + getArticleDetail(); + mView.showCommentSuccess(respComment); + mEmptyView.showCommentSuccess(respComment); + } + } else { + mView.showCommentError(resultBean.getMessage()); + mEmptyView.showCommentError(resultBean.getMessage()); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + mView.showCommentError("评论失败"); + mEmptyView.showCommentError("评论失败"); + } + } + }); + } + + + @Override + public void uploadBehaviors(final List behaviors) { + API.addBehaviors(new Gson().toJson(behaviors), new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + // TODO: 2017/5/25 不需要处理失败的情况 + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.getCode() == 1) { + DBManager.getInstance() + .delete(Behavior.class, "id<=?", String.valueOf(behaviors.get(behaviors.size() - 1).getId())); + AppConfig.getAppConfig(OSCApplication.getInstance()).set("upload_behavior_time", bean.getTime()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + private static String[] removeImgs(String[] imgs) { + if (imgs == null || imgs.length == 0) + return null; + List list = new ArrayList<>(); + for (String img : imgs) { + if (!TextUtils.isEmpty(img)) { + if (img.startsWith("http")) { + list.add(img); + } + } + } + return CollectionUtil.toArray(list, String.class); + } + + @Override + public String formatTextCount(int count) { + String text = String.valueOf(count); + if (count < 1000) + return String.format(" %s ", text); + if (count > 1000 && count < 10000) + return String.format(" %s,%s ", text.substring(0, 1), text.substring(1, text.length())); + if (count > 10000 && count < 100000) + return String.format(" %s,%s ", text.substring(0, 2), text.substring(2, text.length())); + if (count > 100000 && count < 1000000) + return String.format(" %s,%s ", text.substring(0, 3), text.substring(3, text.length())); + if (count > 1000000 && count < 10000000) + return String.format(" %s,%s,%s ", text.substring(0, 1), text.substring(1, 4), text.substring(4, text.length())); + return String.format(" %s ", String.valueOf(count)); + } + + @Override + public String formatTime(long time) { + if (time < 60) { + return String.format(" %s ", time); + } + if (time >= 3600) { + return " 1 "; + } + return String.format(" %s ", time / 60 + 1); + } + + @Override + public String formatTimeUnit(long time) { + if (time >= 3600) { + return "小时"; + } else if (time >= 60) { + return "分钟"; + } else + return "秒"; + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/CommentView.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/CommentView.java new file mode 100644 index 0000000000000000000000000000000000000000..12f17e6cfb419b13fca16a18b26912a69a1c786d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/detail/CommentView.java @@ -0,0 +1,350 @@ +package net.oschina.app.improve.main.synthesize.detail; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.comment.Refer; +import net.oschina.app.improve.bean.comment.Vote; +import net.oschina.app.improve.comment.CommentsUtil; +import net.oschina.app.improve.main.synthesize.comment.ArticleCommentActivity; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; +import net.oschina.app.widget.TweetTextView; +import net.oschina.common.utils.CollectionUtil; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by fei + * on 2016/11/16. + * desc: 资讯、问答、博客、翻译、活动、软件详情评论列表当中进行展示的子view. + * 包括直接渲染出评论下的refer和reply + */ +public class CommentView extends FrameLayout implements View.OnClickListener { + + private Article mArticle; + private String mKey; + private TextView mTitle; + private TextView mSeeMore; + private LinearLayout mLayComments; + private LinearLayout mLinearComment, mLinearTip; + private OnCommentClickListener mListener; + + + public CommentView(Context context) { + super(context); + init(); + } + + public CommentView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public CommentView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + LayoutInflater inflater = LayoutInflater.from(getContext()); + inflater.inflate(R.layout.layout_detail_article_comment, this, true); + mTitle = (TextView) findViewById(R.id.tv_comment); + mLinearComment = (LinearLayout) findViewById(R.id.ll_comment); + mLayComments = (LinearLayout) findViewById(R.id.lay_detail_comment); + mLinearTip = (LinearLayout) findViewById(R.id.ll_tip); + mSeeMore = (TextView) findViewById(R.id.tv_see_more_comment); + mLinearTip.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mListener != null) { + mListener.onShowComment(v); + } + } + }); + } + + public void setTitle(String title) { + if (!TextUtils.isEmpty(title)) { + mTitle.setText(title); + } + } + + + /** + * @return TypeToken + */ + Type getCommentType() { + return new TypeToken>>() { + }.getType(); + } + + /** + * @return TypeToken + */ + Type getVoteType() { + return new TypeToken>() { + }.getType(); + } + + public void setCommentCount(Article article) { + mSeeMore.setText(String.format("查看所有 %s 条评论", article.getCommentCount())); + } + + public void init(Article mArticle, String key, final int mCatalog, + final OnCommentClickListener onCommentClickListener) { + this.mArticle = mArticle; + this.mKey = key; + + mSeeMore.setVisibility(View.GONE); + mSeeMore.setText(String.format("查看所有 %s 条评论", mArticle.getCommentCount())); + //setVisibility(GONE); + mLinearComment.setVisibility(GONE); + mLinearTip.setVisibility(GONE); + + OSChinaApi.getArticleComments(mKey, mCatalog, null, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (throwable != null) + throwable.printStackTrace(); + } + + @SuppressLint("DefaultLocale") + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + + + ResultBean> resultBean = AppOperator.createGson().fromJson(responseString, getCommentType()); + if (resultBean.isSuccess()) { + + List comments = resultBean.getResult().getItems(); + mSeeMore.setText(String.format("查看所有 %s 条评论", comments.size())); + setTitle(String.format("%s", getResources().getString(R.string.hot_comment_hint))); + mSeeMore.setVisibility(VISIBLE); + mSeeMore.setOnClickListener(CommentView.this); + Comment[] array = CollectionUtil.toArray(comments, Comment.class); + initComment(array, onCommentClickListener); + }else { + mLinearComment.setVisibility(View.GONE); + mLinearTip.setVisibility(View.VISIBLE); + } + + } catch (Exception e) { + onFailure(statusCode, headers, responseString, e); + } + } + }); + } + + private void initComment(final Comment[] comments, final OnCommentClickListener onCommentClickListener) { + this.mListener = onCommentClickListener; + if (mLayComments != null) + mLayComments.removeAllViews(); + if (comments != null && comments.length > 0) { +// mLayComments.setVisibility(VISIBLE); + mLinearComment.setVisibility(VISIBLE); + + for (int i = 0, len = comments.length; i < len; i++) { + final Comment comment = comments[i]; + if (comment != null) { + final ViewGroup lay = insertComment(true, comment); + lay.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mListener != null) { + mListener.onClick(v, comment); + } + } + }); + + mLayComments.addView(lay); + if (i == len - 1) { + lay.findViewById(R.id.line).setVisibility(GONE); + } else { + lay.findViewById(R.id.line).setVisibility(View.VISIBLE); + } + } + } + } else { + mLinearComment.setVisibility(View.GONE); + mLinearTip.setVisibility(View.VISIBLE); + } + } + + + @SuppressWarnings("all") + @SuppressLint("DefaultLocale") + private ViewGroup insertComment(final boolean first, final Comment comment) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + @SuppressLint("InflateParams") ViewGroup lay = (ViewGroup) inflater.inflate(R.layout.lay_comment_item, null, false); + + IdentityView identityView = (IdentityView) lay.findViewById(R.id.identityView); + PortraitView ivAvatar = (PortraitView) lay.findViewById(R.id.iv_avatar); + identityView.setup(comment.getAuthor()); + ivAvatar.setup(comment.getAuthor()); + ivAvatar.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + OtherUserHomeActivity.show(getContext(), comment.getAuthor().getId()); + } + }); + final TextView tvVoteCount = (TextView) lay.findViewById(R.id.tv_vote_count); + tvVoteCount.setText(String.valueOf(comment.getVote())); + final ImageView ivVoteStatus = (ImageView) lay.findViewById(R.id.btn_vote); + + //ivComment.setEnabled(true); + tvVoteCount.setText(String.valueOf(comment.getVote())); + tvVoteCount.setVisibility(View.VISIBLE); + ivVoteStatus.setVisibility(View.VISIBLE); + + if (comment.getVoteState() == 1) { + ivVoteStatus.setImageResource(R.mipmap.ic_thumbup_actived); + ivVoteStatus.setTag(true); + } else if (comment.getVoteState() == 0) { + ivVoteStatus.setImageResource(R.mipmap.ic_thumb_normal); + ivVoteStatus.setTag(null); + } + + ivVoteStatus.setOnClickListener(new OnClickListener() { + @Override + public void onClick(final View v) { + handVote(); + } + + private void handVote() { + if (ivVoteStatus.getTag() != null || comment.getVoteState() == 1) { + return; + } + if (!AccountHelper.isLogin()) { + LoginActivity.show(getContext()); + return; + } + if (!TDevice.hasInternet()) { + AppContext.showToast(getResources().getString(R.string.state_network_error), Toast.LENGTH_SHORT); + return; + } + + OSChinaApi.voteArticleComment(comment.getId(), comment.getAuthor().getId(), new TextHttpResponseHandler() { + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + requestFailureHint(throwable); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, getVoteType()); + if (resultBean.isSuccess()) { + Vote vote = resultBean.getResult(); + if (vote != null) { + if (vote.getVoteState() == 1) { + comment.setVoteState(1); + ivVoteStatus.setTag(true); + ivVoteStatus.setImageResource(R.mipmap.ic_thumbup_actived); + } else if (vote.getVoteState() == 0) { + comment.setVoteState(0); + ivVoteStatus.setTag(null); + ivVoteStatus.setImageResource(R.mipmap.ic_thumb_normal); + } + long voteVoteCount = vote.getVote(); + comment.setVote(voteVoteCount); + tvVoteCount.setText(String.valueOf(voteVoteCount)); + } + AppContext.showToastShort("操作成功!!!"); + } else { + AppContext.showToastShort(resultBean.getMessage()); + } + } + + }); + } + }); + + String name = comment.getAuthor().getName(); + if (TextUtils.isEmpty(name)) { + name = getResources().getString(R.string.martian_hint); + } + + ((TextView) lay.findViewById(R.id.tv_name)).setText(name); + + ((TextView) lay.findViewById(R.id.tv_pub_date)).setText( + String.format("%s", StringUtils.formatSomeAgo(comment.getPubDate()))); + + TweetTextView content = ((TweetTextView) lay.findViewById(R.id.tv_content)); + CommentsUtil.formatHtml(getResources(), content, comment.getContent()); + Refer[] refers = comment.getRefer(); + + if (refers != null && refers.length > 0) { + View view = CommentsUtil.getReferLayout(inflater, refers, 0); + lay.addView(view, lay.indexOfChild(content)); + } + + if (!first) { + addView(lay, 0); + } + + return lay; + } + + @Override + public void onClick(View v) { + if (!TextUtils.isEmpty(mKey) && mArticle != null) { + ArticleCommentActivity.show((AppCompatActivity) getContext(), mArticle); + } + } + + /** + * request network error + * + * @param throwable throwable + */ + protected void requestFailureHint(Throwable throwable) { + AppContext.showToastShort(R.string.request_error_hint); + if (throwable != null) { + throwable.printStackTrace(); + } + } + + public interface OnCommentClickListener { + void onClick(View view, Comment comment); + + void onShowComment(View view); + } + +} + + + diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/english/EnglishArticleAdapter.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/EnglishArticleAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..d54d72562ba0a57c31d6b1e4cc666137f8ef57bb --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/EnglishArticleAdapter.java @@ -0,0 +1,307 @@ +package net.oschina.app.improve.main.synthesize.english; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.RecyclerView; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.ImageSpan; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.main.synthesize.DataFormat; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.media.Util; +import net.oschina.app.util.TDevice; + +/** + * 英文推荐界面 + * Created by huanghaibin on 2017/10/23. + */ + +public class EnglishArticleAdapter extends BaseRecyclerAdapter
    implements BaseRecyclerAdapter.OnLoadingHeaderCallBack { + + private static final int VIEW_TYPE_NOT_IMG = 1; + private static final int VIEW_TYPE_ONE_IMG = 2; + private static final int VIEW_TYPE_THREE_IMG = 3; + + private static int WIDTH = 0; + private static final String FORMAT = "!/both/330x246/quality/100"; + private RequestManager mLoader; + private OSCApplication.ReadState mReadState; + + public EnglishArticleAdapter(Context context, int mode) { + super(context, mode); + mReadState = OSCApplication.getReadState("sub_list"); + setOnLoadingHeaderCallBack(this); + mLoader = Glide.with(mContext); + WIDTH = (Util.getScreenWidth(context) - Util.dipTopx(context, 48)) / 3; + } + + @Override + public int getItemViewType(int position) { + Article article = getItem(position); + if (article != null) { + String imgs[] = article.getImgs(); + if (imgs == null || imgs.length == 0) + return VIEW_TYPE_NOT_IMG; + if (imgs.length < 3) + return VIEW_TYPE_ONE_IMG; + return VIEW_TYPE_THREE_IMG; + } + return super.getItemViewType(position); + } + + @Override + public RecyclerView.ViewHolder onCreateHeaderHolder(ViewGroup parent) { + return new HeaderHolder(mHeaderView); + } + + @Override + public void onBindHeaderHolder(RecyclerView.ViewHolder holder, int position) { + + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + if (type == VIEW_TYPE_NOT_IMG) { + return new TextHolder(mInflater.inflate(R.layout.item_list_article_english_not_img, parent, false)); + } else if (type == VIEW_TYPE_ONE_IMG) { + return new OneImgHolder(mInflater.inflate(R.layout.item_list_article_english_one_img, parent, false)); + } else { + return new ThreeImgHolder(mInflater.inflate(R.layout.item_list_article_english_three_img, parent, false)); + } + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Article item, int position) { + int type = getItemViewType(position); + Resources resources = mContext.getResources(); + String sourceName = item.getSource(); + String desc = TextUtils.isEmpty(item.getDesc()) ? "" : item.getDesc().replaceFirst("\\s*|\t|\n", ""); + switch (type) { + case VIEW_TYPE_NOT_IMG: + TextHolder h = (TextHolder) holder; + setTag(h.mTextTitle, h.mImageTag, item); + h.mTextDesc.setText(desc); + h.mTextDesc.setVisibility(TextUtils.isEmpty(desc) ? View.GONE : View.VISIBLE); + h.mTextTime.setText(DataFormat.parsePubDate(item.getPubDate())); + h.mTextAuthor.setText(TextUtils.isEmpty(item.getAuthorName()) ? "匿名" : item.getAuthorName()); + h.mTextOrigin.setText(TextUtils.isEmpty(item.getAuthorName()) ? sourceName : item.getAuthorName()); + h.mTextCommentCount.setText(String.valueOf(item.getCommentCount())); + if (mReadState.already(item.getKey())) { + h.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + h.mTextDesc.setTextColor(TDevice.getColor(resources, R.color.text_secondary_color)); + } else { + h.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_title_color)); + h.mTextDesc.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + } + if (TextUtils.isEmpty(item.getTitleTranslated())) { + h.mTextTitleCN.setVisibility(View.GONE); + h.mTextTitleCN.setTextColor(TDevice.getColor(resources, R.color.text_secondary_color)); + } else { + h.mTextTitleCN.setVisibility(View.VISIBLE); + h.mTextTitleCN.setText(item.getTitleTranslated()); + h.mTextTitleCN.setTextColor(TDevice.getColor(resources, R.color.text_secondary_color)); + } + break; + case VIEW_TYPE_ONE_IMG: + OneImgHolder h1 = (OneImgHolder) holder; + setTag(h1.mTextTitle, h1.mImageTag, item); + h1.mFrameImage.getLayoutParams().width = WIDTH; + h1.mTextTime.setText(DataFormat.parsePubDate(item.getPubDate())); + h1.mTextAuthor.setText(TextUtils.isEmpty(item.getAuthorName()) ? "匿名" : item.getAuthorName()); + h1.mTextOrigin.setText(TextUtils.isEmpty(item.getAuthorName()) ? sourceName : item.getAuthorName()); + h1.mTextCommentCount.setText(String.valueOf(item.getCommentCount())); + mLoader.load(item.getImgs()[0] + FORMAT) + .fitCenter() + .error(R.mipmap.ic_split_graph) + .into(h1.mImageView); + if (mReadState.already(item.getKey())) { + h1.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + } else { + h1.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_title_color)); + } + if (TextUtils.isEmpty(item.getTitleTranslated())) { + h1.mTextTitleCN.setVisibility(View.GONE); + h1.mTextTitleCN.setTextColor(TDevice.getColor(resources, R.color.text_secondary_color)); + } else { + h1.mTextTitleCN.setVisibility(View.VISIBLE); + h1.mTextTitleCN.setText(item.getTitleTranslated()); + h1.mTextTitleCN.setTextColor(TDevice.getColor(resources, R.color.text_secondary_color)); + } + break; + case VIEW_TYPE_THREE_IMG: + ThreeImgHolder h2 = (ThreeImgHolder) holder; + setTag(h2.mTextTitle, h2.mImageTag, item); + h2.mTextTime.setText(DataFormat.parsePubDate(item.getPubDate())); + h2.mTextAuthor.setText(TextUtils.isEmpty(item.getAuthorName()) ? "匿名" : item.getAuthorName()); + h2.mTextOrigin.setText(TextUtils.isEmpty(item.getAuthorName()) ? sourceName : item.getAuthorName()); + h2.mTextCommentCount.setText(String.valueOf(item.getCommentCount())); + mLoader.load(item.getImgs()[0] + FORMAT) + .fitCenter() + .error(R.mipmap.ic_split_graph) + .into(h2.mImageOne); + mLoader.load(item.getImgs()[1] + FORMAT) + .fitCenter() + .into(h2.mImageTwo); + mLoader.load(item.getImgs()[2] + FORMAT) + .fitCenter() + .error(R.mipmap.ic_split_graph) + .into(h2.mImageThree); + if (mReadState.already(item.getKey())) { + h2.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + } else { + h2.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_title_color)); + } + if (TextUtils.isEmpty(item.getTitleTranslated())) { + h2.mTextTitleCN.setVisibility(View.GONE); + h2.mTextTitleCN.setTextColor(TDevice.getColor(resources, R.color.text_secondary_color)); + } else { + h2.mTextTitleCN.setVisibility(View.VISIBLE); + h2.mTextTitleCN.setText(item.getTitleTranslated()); + h2.mTextTitleCN.setTextColor(TDevice.getColor(resources, R.color.text_secondary_color)); + } + break; + } + + } + + + private void setTag(TextView textView, ImageView imageView, Article article) { + if (article.getType() == Article.TYPE_QUESTION) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_question); + imageView.setVisibility(View.VISIBLE); + } else if (TypeFormat.isGit(article)) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_gitee); + imageView.setVisibility(View.VISIBLE); + } else if (article.getType() == Article.TYPE_ZB) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_zb); + imageView.setVisibility(View.VISIBLE); + } else if (article.getType() == Article.TYPE_SOFTWARE) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_software); + imageView.setVisibility(View.VISIBLE); + } else if (article.getType() == Article.TYPE_AD) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_ad); + imageView.setVisibility(View.VISIBLE); + } else if (article.getType() == Article.TYPE_TRANSLATE) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_translate); + imageView.setVisibility(View.VISIBLE); + } else { + textView.setText(article.getTitle()); + imageView.setVisibility(View.GONE); + } + } + + private void setEmptyTag(TextView textView, Article article) { + SpannableStringBuilder spannable = new SpannableStringBuilder(); + spannable.append("[icon] "); + spannable.append(article.getTitle()); + Drawable img = mContext.getResources().getDrawable(R.mipmap.tag_empty); + if (img != null) { + img.setBounds(0, 0, img.getIntrinsicWidth(), img.getIntrinsicHeight()); + } + ImageSpan imageSpan = new ImageSpan(img, ImageSpan.ALIGN_BOTTOM); + spannable.setSpan(imageSpan, 0, 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + textView.setText(spannable); + } + + + private static final class TextHolder extends RecyclerView.ViewHolder { + TextView mTextTitle, + mTextTitleCN, + mTextDesc, + mTextTime, + mTextOrigin, + mTextAuthor, + mTextCommentCount; + ImageView mImageTag; + + TextHolder(View itemView) { + super(itemView); + mImageTag = (ImageView) itemView.findViewById(R.id.iv_tag); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_title); + mTextTitleCN = (TextView) itemView.findViewById(R.id.tv_title_cn); + mTextDesc = (TextView) itemView.findViewById(R.id.tv_desc); + mTextTime = (TextView) itemView.findViewById(R.id.tv_time); + mTextOrigin = (TextView) itemView.findViewById(R.id.tv_origin); + mTextAuthor = (TextView) itemView.findViewById(R.id.tv_author); + mTextCommentCount = (TextView) itemView.findViewById(R.id.tv_comment_count); + } + } + + private static final class OneImgHolder extends RecyclerView.ViewHolder { + TextView mTextTitle, + mTextTitleCN, + mTextTime, + mTextOrigin, + mTextAuthor, + mTextCommentCount; + ImageView mImageView, mImageTag; + FrameLayout mFrameImage; + + OneImgHolder(View itemView) { + super(itemView); + mFrameImage = (FrameLayout) itemView.findViewById(R.id.fl_image); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_title); + mTextTitleCN = (TextView) itemView.findViewById(R.id.tv_title_cn); + mTextTime = (TextView) itemView.findViewById(R.id.tv_time); + mImageView = (ImageView) itemView.findViewById(R.id.iv_image); + mTextOrigin = (TextView) itemView.findViewById(R.id.tv_origin); + mTextAuthor = (TextView) itemView.findViewById(R.id.tv_author); + mImageTag = (ImageView) itemView.findViewById(R.id.iv_tag); + mTextCommentCount = (TextView) itemView.findViewById(R.id.tv_comment_count); + } + } + + private static final class ThreeImgHolder extends RecyclerView.ViewHolder { + TextView mTextTitle, + mTextTitleCN, + mTextTime, + mTextOrigin, + mTextAuthor, + mTextCommentCount; + ImageView mImageOne, mImageTwo, mImageThree, mImageTag; + + ThreeImgHolder(View itemView) { + super(itemView); + mImageTag = (ImageView) itemView.findViewById(R.id.iv_tag); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_title); + mTextTitleCN = (TextView) itemView.findViewById(R.id.tv_title_cn); + mTextTime = (TextView) itemView.findViewById(R.id.tv_time); + mImageOne = (ImageView) itemView.findViewById(R.id.iv_img_1); + mImageTwo = (ImageView) itemView.findViewById(R.id.iv_img_2); + mImageThree = (ImageView) itemView.findViewById(R.id.iv_img_3); + mTextOrigin = (TextView) itemView.findViewById(R.id.tv_origin); + mTextAuthor = (TextView) itemView.findViewById(R.id.tv_author); + mTextCommentCount = (TextView) itemView.findViewById(R.id.tv_comment_count); + } + } + + + private static final class HeaderHolder extends RecyclerView.ViewHolder { + HeaderHolder(View itemView) { + super(itemView); + } + } + + +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/english/EnglishArticleContract.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/EnglishArticleContract.java new file mode 100644 index 0000000000000000000000000000000000000000..35fb0130f141ef59169206dafae762c862147033 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/EnglishArticleContract.java @@ -0,0 +1,21 @@ +package net.oschina.app.improve.main.synthesize.english; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.bean.Article; + +/** + * 英文推荐界面 + * Created by huanghaibin on 2017/10/23. + */ + + interface EnglishArticleContract { + + interface View extends BaseListView{ + + } + + interface Presenter extends BaseListPresenter{ + void loadCache(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/english/EnglishArticleFragment.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/EnglishArticleFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..05229da6d97e355cd81e468c78084c13d02725fb --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/EnglishArticleFragment.java @@ -0,0 +1,128 @@ +package net.oschina.app.improve.main.synthesize.english; + +import android.view.View; + +import net.oschina.app.OSCApplication; +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.main.synthesize.detail.ArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.english.detail.EnglishArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.web.WebActivity; +import net.oschina.app.interf.OnTabReselectListener; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; + +import java.util.List; + +/** + * 英文推荐界面 + * Created by huanghaibin on 2017/10/23. + */ + +public class EnglishArticleFragment extends BaseRecyclerFragment implements EnglishArticleContract.View, OnTabReselectListener { + + private OSCApplication.ReadState mReadState; + + public static EnglishArticleFragment newInstance() { + return new EnglishArticleFragment(); + } + + @Override + protected void initWidget(View root) { + new EnglishArticlePresenter(this); + super.initWidget(root); + } + + @Override + protected void initData() { + mReadState = OSCApplication.getReadState("sub_list"); + if (mPresenter != null) { + mPresenter.loadCache(); + } + super.initData(); + mRefreshLayout.setBottomCount(2); + } + + @Override + protected void onItemClick(Article top, int position) { + if (!TDevice.hasWebView(mContext)) + return; + if (top.getType() == 0) { + if (TypeFormat.isGit(top)) { + WebActivity.show(mContext, TypeFormat.formatUrl(top)); + } else { + ArticleDetailActivity.show(mContext, top); + } + } else { + int type = top.getType(); + long id = top.getOscId(); + switch (type) { + case News.TYPE_SOFTWARE: + SoftwareDetailActivity.show(mContext, id); + break; + case News.TYPE_QUESTION: + QuestionDetailActivity.show(mContext, id); + break; + case News.TYPE_BLOG: + BlogDetailActivity.show(mContext, id); + break; + case News.TYPE_TRANSLATE: + NewsDetailActivity.show(mContext, id, News.TYPE_TRANSLATE); + break; + case News.TYPE_EVENT: + EventDetailActivity.show(mContext, id); + break; + case News.TYPE_NEWS: + NewsDetailActivity.show(mContext, id); + break; + case Article.TYPE_ENGLISH: + EnglishArticleDetailActivity.show(mContext, top); + break; + default: + UIHelper.showUrlRedirect(mContext, top.getUrl()); + break; + } + } + mReadState.put(top.getKey()); + mAdapter.updateItem(position); + } + + @Override + public void onScrollToBottom() { + if (mPresenter != null) { + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + mPresenter.onLoadMore(); + } + } + + @Override + public void onTabReselect() { + if (mRecyclerView != null && mPresenter != null) { + mRecyclerView.scrollToPosition(0); + mRefreshLayout.setRefreshing(true); + onRefreshing(); + } + } + + @Override + public void onRefreshSuccess(List
    data) { + super.onRefreshSuccess(data); + if (data.size() < 8) { + mRefreshLayout.setOnLoading(true); + onLoadMore(); + } + } + + @Override + protected BaseRecyclerAdapter
    getAdapter() { + return new EnglishArticleAdapter(mContext, BaseRecyclerAdapter.ONLY_FOOTER); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/english/EnglishArticlePresenter.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/EnglishArticlePresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..ef0c388712e70e43706905b2fed7272b7c302cb8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/EnglishArticlePresenter.java @@ -0,0 +1,207 @@ +package net.oschina.app.improve.main.synthesize.english; + +import android.text.TextUtils; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.target.Target; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.Launcher; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.improve.utils.CacheManager; +import net.oschina.common.utils.CollectionUtil; +import net.oschina.common.utils.StreamUtil; + +import java.io.File; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; + +import cz.msebera.android.httpclient.Header; + +/** + * 英文推荐界面 + * Created by huanghaibin on 2017/10/23. + */ + +class EnglishArticlePresenter implements EnglishArticleContract.Presenter { + private final EnglishArticleContract.View mView; + private String mNextToken; + private static final String CACHE_NAME = "english_article_list"; + private static final int TYPE_ENGLISH = 8000;//获取英文 + + EnglishArticlePresenter(EnglishArticleContract.View nView) { + this.mView = nView; + this.mView.setPresenter(this); + getLaunch(); + } + + @Override + public void loadCache() { + List
    items = CacheManager.readListJson(OSCApplication.getInstance(), CACHE_NAME, Article.class); + if (items != null) { + mView.onRefreshSuccess(items); + mView.onComplete(); + } + } + + @Override + public void onRefreshing() { + OSChinaApi.getArticles( + OSCSharedPreference.getInstance().getDeviceUUID(), + TYPE_ENGLISH, + "", + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + try { + mView.showNotMore(); + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.onComplete(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + PageBean
    pageBean = bean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List
    list = pageBean.getItems(); + for (Article article : list) { + article.setImgs(removeImgs(article.getImgs())); + } + CacheManager.saveToJson(OSCApplication.getInstance(), CACHE_NAME, list); + mView.onRefreshSuccess(list); + if (list.size() == 0) { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + OSChinaApi.getArticles( + OSCSharedPreference.getInstance().getDeviceUUID(), + TYPE_ENGLISH, + mNextToken, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + PageBean
    pageBean = bean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List
    list = pageBean.getItems(); + for (Article article : list) { + article.setImgs(removeImgs(article.getImgs())); + } + mView.onLoadMoreSuccess(list); + if (list.size() == 0) { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + private static String[] removeImgs(String[] imgs) { + if (imgs == null || imgs.length == 0) + return null; + List list = new ArrayList<>(); + for (String img : imgs) { + if (!TextUtils.isEmpty(img)) { + if (img.startsWith("http")) { + list.add(img); + } + } + } + return CollectionUtil.toArray(list, String.class); + } + + private static void getLaunch() { + OSChinaApi.getLauncher(new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess() && bean.getResult() != null) { + CacheManager.saveToJson(OSCApplication.getInstance(), "Launcher.json", bean.getResult()); + saveAdImage(bean.getResult()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + private static void saveAdImage(Launcher launcher) { + final Future future = Glide.with(OSCApplication.getInstance()) + .load(launcher.getImgUrl()) + .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL); + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + try { + File sourceFile = future.get(); + if (sourceFile == null || !sourceFile.exists()) + return; + String savePath = OSCApplication.getInstance().getCacheDir() + "/launcher"; + final File saveFile = new File(savePath); + StreamUtil.copyFile(sourceFile, saveFile); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/english/detail/EnglishArticleDetailActivity.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/detail/EnglishArticleDetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..53a94efce13d9acdda506f5b06dc28795428e5eb --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/detail/EnglishArticleDetailActivity.java @@ -0,0 +1,467 @@ +package net.oschina.app.improve.main.synthesize.english.detail; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Bitmap; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.widget.LinearLayout; + +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.bean.Report; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.detail.v2.ReportDialog; +import net.oschina.app.improve.main.ClipManager; +import net.oschina.app.improve.main.OnDoubleTouchListener; +import net.oschina.app.improve.main.synthesize.comment.ArticleCommentActivity; +import net.oschina.app.improve.main.synthesize.detail.CommentView; +import net.oschina.app.improve.media.Util; +import net.oschina.app.improve.share.ShareDialog; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.CommentShareView; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.improve.widget.adapter.OnKeyArrivedListenerAdapterV2; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.TDevice; + +import java.util.List; + +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * 英文详情界面 + * Created by huanghaibin on 2018/1/15. + */ + +public class EnglishArticleDetailActivity extends BackActivity implements + CommentView.OnCommentClickListener, + EnglishArticleDetailContract.EmptyView, + EasyPermissions.PermissionCallbacks, + Runnable { + + + private MenuItem mMenuTra; + private CommentBar mDelegation; + private String mCommentHint; + private Article mArticle; + private long mCommentId; + private long mCommentAuthorId; + private CommentShareView mShareView; + private AlertDialog mShareCommentDialog; + private ShareDialog mShareDialog; + private EmptyLayout mEmptyLayout; + private EnglishArticleDetailPresenter mPresenter; + protected Comment mComment; + protected boolean mInputDoubleEmpty = false; + + public static void show(Context context, Article article) { + Intent intent = new Intent(context, EnglishArticleDetailActivity.class); + intent.putExtra("article", article); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_english_article_detail; + } + + @Override + protected void initWidget() { + super.initWidget(); + } + + @SuppressLint("ClickableViewAccessibility") + @SuppressWarnings("all") + @Override + protected void initData() { + super.initData(); + setStatusBarDarkMode(); + setDarkToolBar(); + mArticle = (Article) getIntent().getSerializableExtra("article"); + EnglishArticleDetailFragment mFragment = EnglishArticleDetailFragment.newInstance(mArticle); + mPresenter = new EnglishArticleDetailPresenter(mFragment, this, mArticle); + addFragment(R.id.fl_content, mFragment); + + LinearLayout layComment = (LinearLayout) findViewById(R.id.ll_comment); + if (TextUtils.isEmpty(mCommentHint)) + mCommentHint = getString(R.string.pub_comment_hint); + mDelegation = CommentBar.delegation(this, layComment); + mDelegation.setCommentHint(mCommentHint); + mDelegation.getBottomSheet().getEditText().setHint(mCommentHint); + //mDelegation.setFavDrawable(mBean.isFavorite() ? R.drawable.ic_faved : R.drawable.ic_fav); + mShareView = (CommentShareView) findViewById(R.id.shareView); + mEmptyLayout = (EmptyLayout) findViewById(R.id.lay_error); + mEmptyLayout.setOnLayoutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mEmptyLayout.getErrorState() != EmptyLayout.NETWORK_LOADING) { + mEmptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + mPresenter.getArticleDetail(); + } + } + }); + + mEmptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + mDelegation.setFavListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if ((AccountHelper.isLogin())) { + mPresenter.fav(); + } else { + LoginActivity.show(EnglishArticleDetailActivity.this, 1); + } + } + }); + + mDelegation.getBottomSheet().setMentionListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if ((AccountHelper.isLogin())) { + UserSelectFriendsActivity.show(EnglishArticleDetailActivity.this, mDelegation.getBottomSheet().getEditText()); + } else { + LoginActivity.show(EnglishArticleDetailActivity.this, 1); + } + } + }); + + + mDelegation.setCommentCountListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ArticleCommentActivity.show(EnglishArticleDetailActivity.this, mArticle); + } + }); + + mDelegation.getBottomSheet().getEditText().setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL) { + handleKeyDel(); + } + return false; + } + }); + mDelegation.getBottomSheet().hideSyncAction(); + mDelegation.getBottomSheet().setCommitListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showLoadingDialog("正在提交评论..."); + if (mDelegation == null) return; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(false); + mPresenter.putArticleComment(mDelegation.getBottomSheet().getCommentText(), + mCommentId, + mCommentAuthorId + ); + } + }); + mDelegation.getBottomSheet().getEditText().setOnKeyArrivedListener(new OnKeyArrivedListenerAdapterV2(this)); + + if (mShareView != null) { + mShareCommentDialog = DialogHelper.getRecyclerViewDialog(this, new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + switch (position) { + case 0: + TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(mComment.getContent())); + break; + case 1: + if (!AccountHelper.isLogin()) { + LoginActivity.show(EnglishArticleDetailActivity.this, 1); + return; + } + if (mComment.getAuthor() == null || mComment.getAuthor().getId() == 0) { + SimplexToast.show(EnglishArticleDetailActivity.this, "该用户不存在"); + return; + } + mCommentId = mComment.getId(); + mCommentAuthorId = mComment.getAuthor().getId(); + mDelegation.getCommentText().setHint(String.format("%s %s", getResources().getString(R.string.reply_hint), mComment.getAuthor().getName())); + mDelegation.getBottomSheet().show(String.format("%s %s", getResources().getString(R.string.reply_hint), mComment.getAuthor().getName())); + break; + case 2: + mShareView.init(mArticle.getTitle(), mComment); + saveToFileByPermission(); + break; + } + mShareCommentDialog.dismiss(); + } + }).create(); + } + mDelegation.setCommentCount(mArticle.getCommentCount()); + + mShareDialog = new ShareDialog(this); + mShareDialog.setTitle(mArticle.getTitle()); + mShareDialog.init(this, mArticle.getTitle(), mArticle.getDesc(), mArticle.getUrl()); + if (mArticle.getImgs() != null && mArticle.getImgs().length != 0) { + getImageLoader().load(mArticle.getImgs()[0]) + .asBitmap() + .centerCrop() + .listener(new RequestListener() { + @Override + public boolean onException(Exception e, String model, Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(Bitmap resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { + if (isDestroy()) + return false; + mShareDialog.setThumbBitmap(resource); + return false; + } + }) + .into(Util.dipTopx(this, 80), + Util.dipTopx(this, 80)); + } + + + mToolBar.setOnTouchListener(new OnDoubleTouchListener() { + @Override + public void onMultiTouch(View v, MotionEvent event, int touchCount) { + if (touchCount == 2) { + mPresenter.scrollToTop(); + } + } + }); + } + + @Override + public void showTranslateChange(boolean isEnglish) { + dismissLoadingDialog(); + if (mMenuTra == null) + return; + mMenuTra.setIcon(isEnglish ? R.mipmap.ic_translate_en : R.mipmap.ic_translate); + } + + @Override + public void showTranslateFailure(String message) { + dismissLoadingDialog(); + } + + @Override + public void showReport() { + if (!AccountHelper.isLogin()) { + LoginActivity.show(this); + return; + } + if (mArticle != null) { + ReportDialog.create(this, 0, mArticle.getUrl(), Report.TYPE_ARTICLE, mArticle.getKey()).show(); + } + } + + @SuppressLint("SetTextI18n") + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_english_detail, menu); + mMenuTra = menu.getItem(0); + if (mArticle == null || TextUtils.isEmpty(mArticle.getTitleTranslated())) { + mMenuTra.setVisible(false); + mMenuTra.setIcon(!TextUtils.isEmpty(mArticle.getTitleTranslated()) ? R.mipmap.ic_translate_en : R.mipmap.ic_translate); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + if (mArticle != null) { + ClipManager.IS_SYSTEM_URL = true; + mShareDialog.show(); + } + break; + case R.id.menu_translate: + if(!mPresenter.hasGetDetail()){ + return false; + } + showLoadingDialog("正在获取内容..."); + mPresenter.translate(); + break; + } + return false; + } + + + @Override + public void onClick(View view, Comment comment) { + this.mComment = comment; + if (mShareCommentDialog != null) { + mShareCommentDialog.show(); + } + } + + @Override + public void run() { + hideEmptyLayout(); + } + + @Override + public void onShowComment(View view) { + if (mDelegation != null) { + mDelegation.getBottomSheet().show(mCommentHint); + } + } + + @Override + public void hideEmptyLayout() { + if (isDestroy()) { + return; + } + dismissLoadingDialog(); + mEmptyLayout.postDelayed(new Runnable() { + @Override + public void run() { + if (isDestroy()) + return; + mEmptyLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + mEmptyLayout.setVisibility(View.GONE); + } + }, 600); + } + + @Override + public void showErrorLayout(int errorType) { + if (isDestroy()) { + return; + } + dismissLoadingDialog(); + mEmptyLayout.setErrorType(errorType); + } + + @Override + public void showCommentSuccess(Comment comment) { + if (isDestroyed()) + return; + if (mDelegation == null) + return; + mCommentId = 0; + mCommentAuthorId = 0; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(true); + AppContext.showToastShort(R.string.pub_comment_success); + mDelegation.getCommentText().setHint(mCommentHint); + mDelegation.getBottomSheet().getEditText().setText(""); + mDelegation.getBottomSheet().getEditText().setHint(mCommentHint); + mDelegation.getBottomSheet().dismiss(); + dismissLoadingDialog(); + SimplexToast.show(this, "评论成功"); + mDelegation.setCommentCount(mArticle.getCommentCount()); + } + + @Override + public void showCommentError(String message) { + if (isDestroy()) { + return; + } + SimplexToast.show(this, message); + } + + @Override + public void showFavReverseSuccess(boolean isFav) { + if (isDestroy()) { + return; + } + mDelegation.setFavDrawable(isFav ? R.drawable.ic_faved : R.drawable.ic_fav); + } + + @Override + public void showFavError() { + if (isDestroy()) { + return; + } + SimplexToast.show(this, "收藏失败"); + } + + @Override + public void showGetDetailSuccess(Article article) { + if (isDestroy()) { + return; + } + dismissLoadingDialog(); + mDelegation.setFavDrawable(article.isFavorite() ? R.drawable.ic_faved : R.drawable.ic_fav); + mDelegation.setCommentCount(article.getCommentCount()); + } + + private void handleKeyDel() { + if (mCommentId != 0) { + if (TextUtils.isEmpty(mDelegation.getBottomSheet().getCommentText())) { + if (mInputDoubleEmpty) { + mCommentId = 0; + mCommentAuthorId = 0; + mDelegation.setCommentHint(mCommentHint); + mDelegation.getBottomSheet().getEditText().setHint(mCommentHint); + } else { + mInputDoubleEmpty = true; + } + } else { + mInputDoubleEmpty = false; + } + } + } + + private static final int PERMISSION_ID = 0x0001; + + @SuppressWarnings("unused") + @AfterPermissionGranted(PERMISSION_ID) + public void saveToFileByPermission() { + String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + if (EasyPermissions.hasPermissions(this, permissions)) { + mShareView.share(); + } else { + EasyPermissions.requestPermissions(this, "请授予文件读写权限", PERMISSION_ID, permissions); + } + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + DialogHelper.getConfirmDialog(this, "", "没有权限, 你需要去设置中开启读取手机存储权限.", "去设置", "取消", false, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + //finish(); + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //finish(); + } + }).show(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/english/detail/EnglishArticleDetailContract.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/detail/EnglishArticleDetailContract.java new file mode 100644 index 0000000000000000000000000000000000000000..81dfd8533dfc9bf31bd5abbce34ea7c5c3ec6ffd --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/detail/EnglishArticleDetailContract.java @@ -0,0 +1,75 @@ +package net.oschina.app.improve.main.synthesize.english.detail; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.comment.Comment; + +/** + * 英文详情界面 + * Created by huanghaibin on 2018/1/15. + */ + +interface EnglishArticleDetailContract { + + interface EmptyView { + + void showCommentSuccess(Comment comment); + + void showCommentError(String message); + + void showFavReverseSuccess(boolean isFav); + + void showFavError(); + + void showGetDetailSuccess(Article article); + + void hideEmptyLayout(); + + void showErrorLayout(int errorType); + + void showReport(); + + void showTranslateChange(boolean isEnglish); + + void showTranslateFailure(String message); + } + + interface View extends BaseListView { + void showScrollToTop(); + + void showCommentSuccess(Comment comment); + + void showCommentError(String message); + + void showGetDetailSuccess(Article article); + + void showTranslateSuccess(Article article,String content); + + void showTranslateFailure(String message); + } + + interface Presenter extends BaseListPresenter { + void getArticleDetail(); + + void putArticleComment(String content, long referId, long reAuthorId); + + void addClickCount(); + + void fav(); + + void scrollToTop(); + + String formatTextCount(int count); + + String formatTime(long time); + + String formatTimeUnit(long time); + + void translate(); + + void report(); + + boolean hasGetDetail(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/english/detail/EnglishArticleDetailFragment.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/detail/EnglishArticleDetailFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..241298bd300333baf6a8e15c374d3dadf7c36367 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/detail/EnglishArticleDetailFragment.java @@ -0,0 +1,312 @@ +package net.oschina.app.improve.main.synthesize.english.detail; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.zhy.view.flowlayout.FlowLayout; +import com.zhy.view.flowlayout.TagAdapter; +import com.zhy.view.flowlayout.TagFlowLayout; + +import net.oschina.app.R; +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.Tag; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.main.synthesize.DataFormat; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.main.synthesize.detail.ArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.detail.CommentView; +import net.oschina.app.improve.main.synthesize.english.EnglishArticleAdapter; +import net.oschina.app.improve.main.synthesize.web.ArticleWebActivity; +import net.oschina.app.improve.main.synthesize.web.WebActivity; +import net.oschina.app.improve.widget.OWebView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.UIHelper; + +import java.util.List; + +/** + * 英文详情 + * Created by huanghaibin on 2018/1/17. + */ + +public class EnglishArticleDetailFragment extends BaseRecyclerFragment + implements EnglishArticleDetailContract.View, + View.OnClickListener { + + private OWebView mWebView; + private CommentView mCommentView; + private Article mArticle; + private View mHeaderView; + private TagFlowLayout mFlowLayout; + + private TextView mTextCount; + private TextView mTextTime; + private TextView mTextTimeUnit; + private LinearLayout mLinearCount; + + static EnglishArticleDetailFragment newInstance(Article article) { + Bundle bundle = new Bundle(); + bundle.putSerializable("article", article); + EnglishArticleDetailFragment fragment = new EnglishArticleDetailFragment(); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_english_article_detail; + } + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + mArticle = (Article) bundle.getSerializable("article"); + } + + @SuppressLint("InflateParams") + @Override + protected void initData() { + mHeaderView = mInflater.inflate(R.layout.layout_english_article_detail_header, null); + mAdapter.setHeaderView(mHeaderView); + mWebView = (OWebView) mHeaderView.findViewById(R.id.webView); + mFlowLayout = (TagFlowLayout) mHeaderView.findViewById(R.id.flowLayout); + TextView tv_title = (TextView) mHeaderView.findViewById(R.id.tv_title); + TextView tv_name = (TextView) mHeaderView.findViewById(R.id.tv_name); + TextView tv_pub_date = (TextView) mHeaderView.findViewById(R.id.tv_pub_date); + TextView tv_origin = (TextView) mHeaderView.findViewById(R.id.tv_origin); + tv_title.setText(mArticle.getTitle()); + tv_name.setText(TextUtils.isEmpty(mArticle.getAuthorName()) ? "匿名" : mArticle.getAuthorName()); + tv_pub_date.setText(DataFormat.parsePubDate(mArticle.getPubDate())); + PortraitView portraitView = (PortraitView) mHeaderView.findViewById(R.id.iv_avatar); + tv_origin.setText(mArticle.getSource()); + Author author = new Author(); + author.setName(mArticle.getAuthorName()); + portraitView.setup(author); + mCommentView = (CommentView) mHeaderView.findViewById(R.id.commentView); + mCommentView.setTitle("热门评论"); + mCommentView.init(mArticle, mArticle.getKey(), 2, (CommentView.OnCommentClickListener) mContext); + mRefreshLayout.post(new Runnable() { + @Override + public void run() { + mRefreshLayout.setRefreshing(false); + if (mPresenter == null) + return; + mPresenter.getArticleDetail(); + mPresenter.onRefreshing(); + } + }); + + mLinearCount = (LinearLayout) mHeaderView.findViewById(R.id.ll_count); + mFlowLayout = (TagFlowLayout) mHeaderView.findViewById(R.id.flowLayout); + mTextCount = (TextView) mHeaderView.findViewById(R.id.tv_text_count); + mTextTime = (TextView) mHeaderView.findViewById(R.id.tv_text_time); + mTextTimeUnit = (TextView) mHeaderView.findViewById(R.id.tv_text_time_unit); + mHeaderView.findViewById(R.id.ll_read).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ArticleWebActivity.show(mContext, mArticle); + } + }); + mHeaderView.findViewById(R.id.ll_report).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mPresenter.report(); + } + }); + } + + @Override + public void onClick(View v) { + + } + + @Override + protected void onItemClick(Article top, int position) { + if (top.getType() == 0) { + if (TypeFormat.isGit(top)) { + WebActivity.show(mContext, TypeFormat.formatUrl(top)); + } else { + ArticleDetailActivity.show(mContext, top); + } + } else { + try { + int type = top.getType(); + long id = top.getOscId(); + switch (type) { + case News.TYPE_SOFTWARE: + SoftwareDetailActivity.show(mContext, id); + break; + case News.TYPE_QUESTION: + QuestionDetailActivity.show(mContext, id); + break; + case News.TYPE_BLOG: + BlogDetailActivity.show(mContext, id); + break; + case News.TYPE_TRANSLATE: + NewsDetailActivity.show(mContext, id, News.TYPE_TRANSLATE); + break; + case News.TYPE_EVENT: + EventDetailActivity.show(mContext, id); + break; + case News.TYPE_NEWS: + NewsDetailActivity.show(mContext, id); + break; + case Article.TYPE_ENGLISH: + EnglishArticleDetailActivity.show(mContext, top); + break; + default: + UIHelper.showUrlRedirect(mContext, top.getUrl()); + break; + } + + } catch (Exception e) { + e.printStackTrace(); + ArticleDetailActivity.show(mContext, top); + } + } + } + + @Override + public void onScrollToBottom() { + if (mPresenter != null) { + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + mPresenter.onLoadMore(); + } + } + + @Override + public void onRefreshing() { + super.onRefreshing(); + if (mCommentView != null) + mCommentView.init(mArticle, mArticle.getKey(), 2, (CommentView.OnCommentClickListener) mContext); + } + + @Override + public void onRefreshSuccess(List
    data) { + super.onRefreshSuccess(data); + mRefreshLayout.setCanLoadMore(true); + } + + @Override + public void onLoadMoreSuccess(List
    data) { + super.onLoadMoreSuccess(data); + if (data != null && data.size() > 0) { + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + } else { + mRefreshLayout.setCanLoadMore(false); + } + } + + @Override + public void showScrollToTop() { + if (mRecyclerView != null) { + mRecyclerView.scrollToPosition(0); + } + } + + @Override + public void showCommentSuccess(Comment comment) { + + } + + @Override + public void showCommentError(String message) { + + } + + @Override + public void onComplete() { + super.onComplete(); + if (mContext == null) + return; + hideOrShowTitle(mAdapter.getItems().size() != 0); + } + + private void hideOrShowTitle(boolean isShow) { + if (isShow) { + mHeaderView.findViewById(R.id.line1).setVisibility(View.VISIBLE); + mHeaderView.findViewById(R.id.line2).setVisibility(View.VISIBLE); + mHeaderView.findViewById(R.id.tv_recommend).setVisibility(View.VISIBLE); + } else { + mHeaderView.findViewById(R.id.line1).setVisibility(View.GONE); + mHeaderView.findViewById(R.id.line2).setVisibility(View.GONE); + mHeaderView.findViewById(R.id.tv_recommend).setVisibility(View.GONE); + mAdapter.setState(BaseRecyclerAdapter.STATE_HIDE, true); + } + } + + @Override + public void showTranslateSuccess(Article article,String content) { + if (mContext == null) + return; + mWebView.loadDetailDataAsync(content, (Runnable) mContext); + mTextCount.setText(mPresenter.formatTextCount(article.getWordCount())); + mTextTime.setText(mPresenter.formatTime(article.getReadTime())); + } + + @Override + public void showTranslateFailure(String message) { + if (mContext == null) + return; + SimplexToast.show(mContext, message); + } + + @SuppressWarnings("all") + @Override + public void showGetDetailSuccess(final Article article) { + if (mContext == null) + return; + mArticle = article; + mLinearCount.setVisibility(article.getWordCount() != 0 ? View.VISIBLE : View.GONE); + mTextCount.setText(mPresenter.formatTextCount(article.getWordCount())); + mTextTime.setText(mPresenter.formatTime(article.getReadTime())); + mTextTimeUnit.setText(mPresenter.formatTimeUnit(article.getReadTime())); + mWebView.loadDetailDataAsync(article.getContent(), (Runnable) mContext); + mCommentView.init(mArticle, mArticle.getKey(), 2, (CommentView.OnCommentClickListener) mContext); + mCommentView.setCommentCount(article); + mFlowLayout.removeAllViews(); + if (article.getiTags() == null || article.getiTags().length == 0) { + mFlowLayout.setVisibility(View.GONE); + return; + } + mFlowLayout.setVisibility(View.VISIBLE); + mFlowLayout.setAdapter(new TagAdapter(article.getiTags()) { + @Override + public View getView(FlowLayout parent, int position, final Tag tag) { + TextView tvTag = (TextView) getActivity().getLayoutInflater().inflate(R.layout.tag_item, mFlowLayout, false); + tvTag.setText(tag.getName()); + + return tvTag; + } + }); + mFlowLayout.setOnTagClickListener(new TagFlowLayout.OnTagClickListener() { + @Override + public boolean onTagClick(View view, int position, FlowLayout parent) { + Tag tag = article.getiTags()[position]; + if (tag.getOscId() != 0) { + SoftwareDetailActivity.show(mContext, tag.getOscId()); + } + return true; + } + }); + } + + @Override + protected BaseRecyclerAdapter
    getAdapter() { + return new EnglishArticleAdapter(mContext, BaseRecyclerAdapter.BOTH_HEADER_FOOTER); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/english/detail/EnglishArticleDetailPresenter.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/detail/EnglishArticleDetailPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..8b2034d7f61bb53cc0595797819bfe2e9a091fc9 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/english/detail/EnglishArticleDetailPresenter.java @@ -0,0 +1,479 @@ +package net.oschina.app.improve.main.synthesize.english.detail; + +import android.text.TextUtils; +import android.util.Log; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.Collection; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.common.utils.CollectionUtil; + +import java.io.Serializable; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * 英文详情界面 + * Created by huanghaibin on 2018/1/15. + */ + +class EnglishArticleDetailPresenter implements EnglishArticleDetailContract.Presenter { + private final EnglishArticleDetailContract.View mView; + private final EnglishArticleDetailContract.EmptyView mEmptyView; + private static final int TYPE_ENGLISH = 8000;//获取英文 + private Article mArticle; + private Article mSource; + private String mNextToken; + private Article mTranslateArticle; + private boolean hasGetDetail; + private boolean hasGetENDetail;//获取了英文 + private boolean isEnglish; + + EnglishArticleDetailPresenter(EnglishArticleDetailContract.View mView, + EnglishArticleDetailContract.EmptyView emptyView, + Article mArticle) { + this.mView = mView; + this.mEmptyView = emptyView; + this.mSource = mArticle; + this.mArticle = mArticle; + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + OSChinaApi.getArticleRecommends( + mArticle.getKey(), + OSCSharedPreference.getInstance().getDeviceUUID(), + TYPE_ENGLISH, + "", + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + try { + mView.showNotMore(); + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + PageBean
    pageBean = bean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List
    list = pageBean.getItems(); + for (Article article : list) { + article.setImgs(removeImgs(article.getImgs())); + } + mView.onRefreshSuccess(list); + if (list.size() == 0) { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + OSChinaApi.getArticleRecommends( + mArticle.getKey(), + OSCSharedPreference.getInstance().getDeviceUUID(), + TYPE_ENGLISH, + mNextToken, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + try { + mView.showNotMore(); + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + PageBean
    pageBean = bean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List
    list = pageBean.getItems(); + for (Article article : list) { + article.setImgs(removeImgs(article.getImgs())); + } + mView.onLoadMoreSuccess(list); + if (list.size() == 0) { + mView.showNotMore(); + } + } else { + mView.showNetworkError(R.string.footer_type_net_error); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void getArticleDetail() { + addClickCount(); + + if (!TextUtils.isEmpty(mArticle.getTitleTranslated())) { + isEnglish = true; + getEnglishDetailCN(); + return; + } + isEnglish = false; + getEnglishDetailEN(); + } + + /** + * 获取英文详情 + */ + private void getEnglishDetailEN() { + OSChinaApi.getArticleDetail(mArticle.getKey(), + OSCSharedPreference.getInstance().getDeviceUUID(), + TYPE_ENGLISH, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mEmptyView.showErrorLayout(EmptyLayout.NETWORK_ERROR); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean
    bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess() && bean.getResult() != null) { + mArticle = bean.getResult(); + hasGetDetail = true; + hasGetENDetail = true; + isEnglish = true; + mView.showGetDetailSuccess(mArticle); + mEmptyView.showTranslateChange(true); + mEmptyView.showGetDetailSuccess(mArticle); + mEmptyView.hideEmptyLayout(); + } else { + mEmptyView.showErrorLayout(EmptyLayout.NODATA); + } + } catch (Exception e) { + e.printStackTrace(); + mEmptyView.showErrorLayout(EmptyLayout.NODATA); + } + } + }); + } + + /** + * 获取翻译详情 + */ + private void getEnglishDetailCN() { + OSChinaApi.translate(mArticle.getKey(), Article.TYPE_ENGLISH, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showTranslateFailure("网络错误"); + mEmptyView.showTranslateFailure("网络错误"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean
    bean = new Gson().fromJson(responseString, type); + if (bean != null) { + if (bean.isSuccess()) { + mTranslateArticle = bean.getResult(); + hasGetDetail = true; + isEnglish = false; + parseTranslate(); + } else { + mEmptyView.showTranslateFailure(bean.getMessage()); + mView.showTranslateFailure(bean.getMessage()); + } + } else { + mEmptyView.showTranslateFailure("网络错误"); + mView.showTranslateFailure("翻译错误"); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showTranslateFailure("翻译错误"); + mEmptyView.showTranslateFailure("网络错误"); + } + } + }); + } + + @Override + public void putArticleComment(String content, long referId, long reAuthorId) { + OSChinaApi.pubArticleComment(mArticle.getKey(), + content, + TYPE_ENGLISH, + referId, + reAuthorId, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.tip_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + mArticle.setCommentCount(mArticle.getCommentCount() + 1); + Comment respComment = resultBean.getResult(); + if (respComment != null) { + getArticleDetail(); + mView.showCommentSuccess(respComment); + mEmptyView.showCommentSuccess(respComment); + } + } else { + mView.showCommentError(resultBean.getMessage()); + mEmptyView.showCommentError(resultBean.getMessage()); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + mView.showCommentError("评论失败"); + mEmptyView.showCommentError("评论失败"); + } + } + }); + } + + @Override + public void addClickCount() { + OSChinaApi.addClickCount(mArticle.getKey(), new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + } + }); + } + + @Override + public void fav() { + + OSChinaApi.articleFav(new Gson().toJson(mSource), + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + Log.e("onFailure", " -- " + statusCode + " -- " + responseString); + mEmptyView.showFavError(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean != null && resultBean.isSuccess()) { + Collection collection = resultBean.getResult(); + mArticle.setFavorite(collection.isFavorite()); + mEmptyView.showFavReverseSuccess(collection.isFavorite()); + } else { + mEmptyView.showFavError(); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + }); + } + + @Override + public void report() { + mEmptyView.showReport(); + } + + + @Override + public void translate() { + if (isEnglish) { + if (mTranslateArticle != null) { + try { + parseTranslate(); + } catch (Exception e) { + mView.showTranslateFailure("网络错误"); + mEmptyView.showTranslateFailure("网络错误"); + e.printStackTrace(); + } + } else { + getEnglishDetailCN(); + } + } else { + if (hasGetENDetail) { + isEnglish = true; + mView.showGetDetailSuccess(mArticle); + mEmptyView.showTranslateChange(true); + } else { + getEnglishDetailEN(); + } + } + } + + /** + * 解析翻译结果 + */ + private void parseTranslate() { + Type type = new TypeToken>() { + }.getType(); + List result = new Gson().fromJson(mTranslateArticle.getContent(), type); + if (result == null || result.size() == 0) { + mView.showTranslateFailure("网络错误"); + return; + } + StringBuilder sb = new StringBuilder(); + for (Translate translate : result) { + sb.append(translate.src); + sb.append("\n"); + sb.append(translate.dest); + } + mView.showTranslateSuccess(mTranslateArticle, sb.toString()); + isEnglish = false; + mEmptyView.showTranslateChange(false); + } + + + @Override + public void scrollToTop() { + mView.showScrollToTop(); + } + + private static String[] removeImgs(String[] imgs) { + if (imgs == null || imgs.length == 0) + return null; + List list = new ArrayList<>(); + for (String img : imgs) { + if (!TextUtils.isEmpty(img)) { + if (img.startsWith("http")) { + list.add(img); + } + } + } + return CollectionUtil.toArray(list, String.class); + } + + @Override + public String formatTextCount(int count) { + String text = String.valueOf(count); + if (count < 1000) + return String.format(" %s ", text); + if (count > 1000 && count < 10000) + return String.format(" %s,%s ", text.substring(0, 1), text.substring(1, text.length())); + if (count > 10000 && count < 100000) + return String.format(" %s,%s ", text.substring(0, 2), text.substring(2, text.length())); + if (count > 100000 && count < 1000000) + return String.format(" %s,%s ", text.substring(0, 3), text.substring(3, text.length())); + if (count > 1000000 && count < 10000000) + return String.format(" %s,%s,%s ", text.substring(0, 1), text.substring(1, 4), text.substring(4, text.length())); + return String.format(" %s ", String.valueOf(count)); + } + + @Override + public String formatTime(long time) { + if (time < 60) { + return String.format(" %s ", time); + } + if (time >= 3600) { + return " 1 "; + } + return String.format(" %s ", time / 60 + 1); + } + + @Override + public String formatTimeUnit(long time) { + if (time >= 3600) { + return "小时"; + } else if (time >= 60) { + return "分钟"; + } else + return "秒"; + } + + @Override + public boolean hasGetDetail() { + return hasGetDetail; + } + + @SuppressWarnings("unused") + private static class Translate implements Serializable { + private int index; + private String src; + private String dest; + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public String getSrc() { + return src; + } + + public void setSrc(String src) { + this.src = src; + } + + + public String getDest() { + return dest; + } + + public void setDest(String dest) { + this.dest = dest; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/pub/PubArticleActivity.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/pub/PubArticleActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..349dc87bfb32bbcfc5bf7b9fe9331859286569b0 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/pub/PubArticleActivity.java @@ -0,0 +1,205 @@ +package net.oschina.app.improve.main.synthesize.pub; + +import android.content.Context; +import android.content.Intent; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.View; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.main.ClipManager; +import net.oschina.app.improve.widget.OSCWebView; +import net.oschina.app.improve.widget.SimplexToast; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 发布分享文章 + * Created by huanghaibin on 2017/12/1. + */ + +public class PubArticleActivity extends BackActivity implements PubArticleContract.View, + OSCWebView.OnFinishListener, + View.OnClickListener { + + @Bind(R.id.et_url) + EditText mTextUrl; + + @Bind(R.id.tv_title) + TextView mTextTitle; + + @Bind(R.id.fl_web) + FrameLayout mFrameWebView; + + OSCWebView mWebView; + + private PubArticlePresenter mPresenter; + + public static void show(Context context, String url) { + Intent intent = new Intent(context, PubArticleActivity.class); + intent.putExtra("url", url); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_pub_article; + } + + @Override + protected void initWidget() { + super.initWidget(); + setDarkToolBar(); + setStatusBarDarkMode(); + mWebView = new OSCWebView(this); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 0); + mWebView.setVisibility(View.INVISIBLE); + mWebView.setLayoutParams(params); + mWebView.setOnFinishFinish(this); + mWebView.setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36"); + mFrameWebView.addView(mWebView); + } + + @Override + protected void initData() { + super.initData(); + Intent intent = getIntent(); + + String mUrl = ""; + String action = intent.getAction();//action + String type = intent.getType();//类型 + + if (Intent.ACTION_SEND.equals(action) && type != null && "text/plain".equals(type)) { + try { + String text = intent.getStringExtra(Intent.EXTRA_TEXT); + if (!TextUtils.isEmpty(text)) { + mUrl = PubArticlePresenter.findUrl(text); + } + } catch (Exception e) { + e.printStackTrace(); + mUrl = ""; + } + + } else { + mUrl = intent.getStringExtra("url"); + } + + if (TextUtils.isEmpty(mUrl)) { + mUrl = ClipManager.getClipUrl(); + } + mTextUrl.setText(mUrl); + mPresenter = new PubArticlePresenter(this); + mTextUrl.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (PubArticlePresenter.checkUrl(s.toString()) && mWebView != null) { + if(mPresenter.isWechatUrl(s.toString())){ + mPresenter.getTitle(s.toString()); + }else { + mWebView.loadUrl(s.toString()); + } + } + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + if(mPresenter.isWechatUrl(mUrl)){ + mPresenter.getTitle(mUrl); + }else { + mWebView.loadUrl(mUrl); + } + } + + @OnClick({R.id.btn_commit}) + @Override + public void onClick(View v) { + mPresenter.putArticle(mTextUrl.getText().toString().trim(), ""); + } + + + @Override + public void onReceivedTitle(String title) { + if (isDestroy()) + return; + mTextTitle.setText(title); + } + + @Override + public void onProgressChange(int progress) { + + } + + @Override + public void onError() { + + } + + @Override + public void onFinish() { + + } + + @Override + public void showGetTitleSuccess(String title) { + if (isDestroy()) + return; + mTextTitle.setText(title); + } + + @Override + public void showGetTitleFailure(String message) { + if (isDestroy()) + return; + mTextTitle.setText(message); + } + + @Override + public void showPubSuccess(int strId) { + if (isDestroyed()) { + return; + } + SimplexToast.show(this, strId); + finish(); + } + + @Override + public void showPubFailure(String message) { + if (isDestroyed()) { + return; + } + SimplexToast.show(this, message); + } + + + @Override + public void setPresenter(PubArticleContract.Presenter presenter) { + + } + + @Override + public void showNetworkError(int strId) { + + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mWebView != null) { + mWebView.onDestroy(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/pub/PubArticleContract.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/pub/PubArticleContract.java new file mode 100644 index 0000000000000000000000000000000000000000..6d572fc5125eab63b7c35e426c1d10f5ebbe6ea4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/pub/PubArticleContract.java @@ -0,0 +1,34 @@ +package net.oschina.app.improve.main.synthesize.pub; + +import net.oschina.app.improve.base.BasePresenter; +import net.oschina.app.improve.base.BaseView; + +/** + * 发布分享文章 + * Created by huanghaibin on 2017/12/1. + */ + +interface PubArticleContract { + + interface View extends BaseView { + void showPubSuccess(int strId); + + void showPubFailure(String message); + + void showGetTitleSuccess(String title); + + void showGetTitleFailure(String message); + } + + interface Presenter extends BasePresenter { + void putArticle(String url, String title); + + void getTitle(String url); + + /** + * 判断是不是微信公众号文章 + * @param url + */ + boolean isWechatUrl(String url); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/pub/PubArticlePresenter.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/pub/PubArticlePresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..8a0e84fbc4006921ff248137f212c21655600f19 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/pub/PubArticlePresenter.java @@ -0,0 +1,145 @@ +package net.oschina.app.improve.main.synthesize.pub; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.AsyncHttpClient; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.base.ResultBean; + +import java.lang.reflect.Type; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import cz.msebera.android.httpclient.Header; + +/** + * 发布分享文章 + * Created by huanghaibin on 2017/12/1. + */ + +class PubArticlePresenter implements PubArticleContract.Presenter { + private final PubArticleContract.View mView; + private static String rule = null; + + PubArticlePresenter(PubArticleContract.View mView) { + this.mView = mView; + this.mView.setPresenter(this); + getWXRule(); + } + + private void getWXRule() { + OSChinaApi.getWXRule(new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + rule = bean.getResult(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @Override + public void putArticle(String url, String title) { + if (!checkUrl(url)) { + mView.showPubFailure("请填写正确的url"); + return; + } + OSChinaApi.putArticle(url, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showPubFailure("投递失败"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean != null) { + if (bean.getCode() == 1) { + mView.showPubSuccess(R.string.pub_article_success); + } else { + mView.showPubFailure(bean.getMessage()); + } + } else { + mView.showPubFailure("投递失败"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + private static final AsyncHttpClient mClient = new AsyncHttpClient(); + + @Override + public void getTitle(String url) { + mClient.get(url, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showGetTitleFailure("获取标题失败"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + mView.showGetTitleSuccess(matcherTitle(responseString)); + } catch (Exception e) { + e.printStackTrace(); + mView.showGetTitleFailure("获取标题失败"); + } + } + }); + } + + @Override + public boolean isWechatUrl(String url) { + return !TextUtils.isEmpty(url) && url.startsWith("https://mp.weixin.qq.com/"); + } + + static boolean checkUrl(String url) { + Pattern pattern = Pattern.compile("^https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]"); + return pattern.matcher(url).find(); + } + + static String findUrl(String text) { + Pattern pattern = Pattern.compile("https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]"); + Matcher matcher = pattern.matcher(text); + if (matcher.find()) { + return matcher.group(0); + } + return ""; + } + + private static String matcherTitle(String response) { + Pattern pattern ; + if(TextUtils.isEmpty(rule)){ + pattern = Pattern.compile("<[^!][^(title)][^(script)][^>]+>([^<][^>]+)]+>"); + }else { + pattern = Pattern.compile(rule); + } + Matcher matcher = pattern.matcher(response); + if (matcher.find()) { + return matcher.group(1).trim(); + } + return "获取标题失败"; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/pub/PubTipActivity.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/pub/PubTipActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..7466ff49d4ae1311fc3d216e53ea0534baf84bef --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/pub/PubTipActivity.java @@ -0,0 +1,87 @@ +package net.oschina.app.improve.main.synthesize.pub; + +import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; +import android.view.View; +import android.view.WindowManager; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.main.update.OSCSharedPreference; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 发布提醒界面 + * Created by huanghaibin on 2017/12/5. + */ + +public class PubTipActivity extends BaseActivity implements View.OnClickListener { + + private static boolean IS_SHOW = false; + + @Bind(R.id.tv_url) + TextView mTextUrl; + + private String mUrl; + + public static void show(Context context, String url) { + if(!AccountHelper.isLogin()){ + LoginActivity.show(context); + return; + } + if (IS_SHOW || !OSCSharedPreference.getInstance().isRelateClip()) { + return; + } + if (TextUtils.isEmpty(url) || + url.startsWith("https://www.oschina.net") || + url.startsWith("https://my.oschina.net")) { + return; + } + IS_SHOW = true; + Intent intent = new Intent(context, PubTipActivity.class); + intent.putExtra("url", url); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_pub_tip; + } + + + @Override + protected void initWindow() { + super.initWindow(); + getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); + } + + @Override + protected void initWidget() { + super.initWidget(); + mUrl = getIntent().getStringExtra("url"); + mTextUrl.setText(mUrl); + } + + @OnClick({R.id.btn_share, R.id.btn_cancel, R.id.fl_content}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_share: + PubArticleActivity.show(this, mUrl); + break; + } + finish(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + IS_SHOW = false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadArticleAdapter.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadArticleAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..d67464ef21569b373234197e632eed1af5d2f0a6 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadArticleAdapter.java @@ -0,0 +1,79 @@ +package net.oschina.app.improve.main.synthesize.read; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.main.synthesize.DataFormat; + +/** + * 文章阅读记录 + * Created by huanghaibin on 2017/12/4. + */ + +public class ReadArticleAdapter extends BaseRecyclerAdapter
    { + ReadArticleAdapter(Context context) { + super(context, NEITHER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ArticleViewHolder(mInflater.inflate(R.layout.item_list_read_article, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Article item, int position) { + ArticleViewHolder h = (ArticleViewHolder) holder; + String type; + switch (item.getType()) { + case Article.TYPE_SOFTWARE: + type = "软件"; + break; + case Article.TYPE_QUESTION: + type = "问答"; + break; + case Article.TYPE_BLOG: + type = "博客"; + break; + case Article.TYPE_TRANSLATE: + type = "翻译"; + break; + case Article.TYPE_EVENT: + type = "活动"; + break; + case Article.TYPE_NEWS: + type = "资讯"; + break; + case Article.TYPE_ZB: + type = "众包"; + break; + default: + type = "链接"; + break; + } + h.mTypeView.setText(type); + h.mTitleView.setText(item.getTitle()); + h.mFavDateText.setText(DataFormat.parsePubDate(item.getPubDate())); + h.mAuthorText.setText(item.getSource()); + h.mCommentCountText.setText(String.valueOf(item.getCommentCount())); + } + + + private class ArticleViewHolder extends RecyclerView.ViewHolder { + private TextView mTypeView, mTitleView, mCommentCountText, mAuthorText, mFavDateText; + + private ArticleViewHolder(View itemView) { + super(itemView); + mTypeView = (TextView) itemView.findViewById(R.id.tv_type); + mTitleView = (TextView) itemView.findViewById(R.id.tv_title); + mCommentCountText = (TextView) itemView.findViewById(R.id.tv_comment_count); + mAuthorText = (TextView) itemView.findViewById(R.id.tv_user); + mFavDateText = (TextView) itemView.findViewById(R.id.tv_fav_date); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadHistoryActivity.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadHistoryActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..2120a7c53840666d04e1d5f8e8220adcd33ae62b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadHistoryActivity.java @@ -0,0 +1,34 @@ +package net.oschina.app.improve.main.synthesize.read; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; + +/** + * 阅读记录 + * Created by huanghaibin on 2017/12/4. + */ + +public class ReadHistoryActivity extends BackActivity { + + public static void show(Context context) { + context.startActivity(new Intent(context, ReadHistoryActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_read_history; + } + + @Override + protected void initWidget() { + super.initWidget(); + setDarkToolBar(); + setStatusBarDarkMode(); + ReadHistoryFragment fragment = ReadHistoryFragment.newInstance(); + addFragment(R.id.fl_content, fragment); + new ReadHistoryPresenter(fragment); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadHistoryContract.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadHistoryContract.java new file mode 100644 index 0000000000000000000000000000000000000000..d7690af74e05f96f017885be4d3c7fd39e17b105 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadHistoryContract.java @@ -0,0 +1,21 @@ +package net.oschina.app.improve.main.synthesize.read; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.bean.Article; + +/** + * 阅读记录 + * Created by huanghaibin on 2017/12/4. + */ + +interface ReadHistoryContract { + + interface View extends BaseListView { + + } + + interface Presenter extends BaseListPresenter { + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadHistoryFragment.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadHistoryFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..1ae444225fb4b69c8bcacab0c2a30c42f52d6b77 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadHistoryFragment.java @@ -0,0 +1,81 @@ +package net.oschina.app.improve.main.synthesize.read; + +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.main.synthesize.detail.ArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.english.detail.EnglishArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.web.WebActivity; +import net.oschina.app.util.UIHelper; + +/** + * 阅读记录 + * Created by huanghaibin on 2017/12/5. + */ + +public class ReadHistoryFragment extends BaseRecyclerFragment + implements ReadHistoryContract.View { + + + static ReadHistoryFragment newInstance() { + return new ReadHistoryFragment(); + } + + @Override + protected void onItemClick(Article top, int position) { + if (top.getType() == 0) { + if (TypeFormat.isGit(top)) { + WebActivity.show(mContext, TypeFormat.formatUrl(top)); + } else { + ArticleDetailActivity.show(mContext, top); + } + } else { + try { + int type = top.getType(); + long id = top.getOscId(); + switch (type) { + case News.TYPE_SOFTWARE: + SoftwareDetailActivity.show(mContext, id); + break; + case News.TYPE_QUESTION: + QuestionDetailActivity.show(mContext, id); + break; + case News.TYPE_BLOG: + BlogDetailActivity.show(mContext, id); + break; + case News.TYPE_TRANSLATE: + NewsDetailActivity.show(mContext, id, News.TYPE_TRANSLATE); + break; + case News.TYPE_EVENT: + EventDetailActivity.show(mContext, id); + break; + case News.TYPE_NEWS: + NewsDetailActivity.show(mContext, id); + break; + case Article.TYPE_ENGLISH: + EnglishArticleDetailActivity.show(mContext,top); + break; + default: + UIHelper.showUrlRedirect(mContext, top.getUrl()); + break; + } + + } catch (Exception e) { + e.printStackTrace(); + ArticleDetailActivity.show(mContext, top); + } + } + } + + @Override + protected BaseRecyclerAdapter
    getAdapter() { + return new ReadArticleAdapter(mContext); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadHistoryPresenter.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadHistoryPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..a664cdbccb0bf79c9d6ded4059e550f9612b3088 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/read/ReadHistoryPresenter.java @@ -0,0 +1,103 @@ +package net.oschina.app.improve.main.synthesize.read; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.common.utils.CollectionUtil; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * 阅读记录 + * Created by huanghaibin on 2017/12/4. + */ + +class ReadHistoryPresenter implements ReadHistoryContract.Presenter { + private final ReadHistoryContract.View mView; + + ReadHistoryPresenter(ReadHistoryContract.View mView) { + this.mView = mView; + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + OSChinaApi.readHistory(null, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + try { + mView.showNotMore(); + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.onComplete(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + List
    list = removeNull(bean.getResult()); + for (Article article : list) { + article.setImgs(removeImgs(article.getImgs())); + } + mView.onRefreshSuccess(list); + if (list.size() == 0) { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.showNotMore(); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + + } + + private static String[] removeImgs(String[] imgs) { + if (imgs == null || imgs.length == 0) + return null; + List list = new ArrayList<>(); + for (String img : imgs) { + if (!TextUtils.isEmpty(img)) { + if (img.startsWith("http")) { + list.add(img); + } + } + } + return CollectionUtil.toArray(list, String.class); + } + + private static List
    removeNull(List
    list) { + List
    articles = new ArrayList<>(); + for (Article article : list) { + if (article != null) { + articles.add(article); + } + } + return articles; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/web/ArticleWebActivity.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/web/ArticleWebActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..3321a4dea84f7790b5b8c01722bdb263c1e55c18 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/web/ArticleWebActivity.java @@ -0,0 +1,245 @@ +package net.oschina.app.improve.main.synthesize.web; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.bean.Report; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.detail.v2.ReportDialog; +import net.oschina.app.improve.main.ClipManager; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.main.synthesize.comment.ArticleCommentActivity; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.improve.widget.adapter.OnKeyArrivedListenerAdapterV2; +import net.oschina.app.util.TDevice; + +/** + * 头条浏览器 + * Created by huanghaibin on 2017/10/28. + */ + +public class ArticleWebActivity extends WebActivity implements ArticleWebContract.View { + private CommentBar mDelegation; + protected boolean mInputDoubleEmpty = false; + protected long mCommentId; + protected long mCommentAuthorId; + private Article mArticle; + private String mCommentHint; + private ArticleWebPresenter mPresenter; + + public static void show(Context context, Article article) { + if (!TDevice.hasWebView(context)) + return; + if (article == null) + return; + Intent intent = new Intent(context, ArticleWebActivity.class); + intent.putExtra("article", article); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_article_web; + } + + @Override + protected void initWidget() { + super.initWidget(); + mArticle = (Article) getIntent().getSerializableExtra("article"); + mPresenter = new ArticleWebPresenter(this, mArticle); + mCommentHint = getString(R.string.pub_comment_hint); + mDelegation = CommentBar.delegation(this, mLinearRoot); + mDelegation.setCommentHint(mCommentHint); + mDelegation.getBottomSheet().getEditText().setHint(mCommentHint); + mDelegation.setFavListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mPresenter.fav(); + } + }); + mDelegation.getBottomSheet().setMentionListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if ((AccountHelper.isLogin())) { + UserSelectFriendsActivity.show(ArticleWebActivity.this, mDelegation.getBottomSheet().getEditText()); + } else { + LoginActivity.show(ArticleWebActivity.this, 1); + } + } + }); + + mDelegation.setCommentCountListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ArticleCommentActivity.show(ArticleWebActivity.this, mArticle); + } + }); + + mDelegation.getBottomSheet().getEditText().setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL) { + handleKeyDel(); + } + return false; + } + }); + mDelegation.setCommentCount(mArticle.getCommentCount()); + mDelegation.getBottomSheet().hideSyncAction(); + mDelegation.getBottomSheet().setCommitListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showLoadingDialog("正在提交评论..."); + if (mDelegation == null) return; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(false); + mPresenter.putArticleComment(mDelegation.getBottomSheet().getCommentText(), + mCommentId, + mCommentAuthorId + ); + } + }); + mDelegation.getBottomSheet().getEditText().setOnKeyArrivedListener(new OnKeyArrivedListenerAdapterV2(this)); + + } + + @Override + protected void initData() { + super.initData(); + mToolBar.setTitle("返回"); + mShareDialog.setTitle(mArticle.getTitle()); + mShareDialog.init(this, mArticle.getTitle(), mArticle.getDesc(), mArticle.getUrl()); + //mWebView.loadUrl(TypeFormat.formatUrl(mArticle)); + getRule(TypeFormat.formatUrl(mArticle)); + } + + @Override + public void showFavReverseSuccess(boolean isFav) { + if (isDestroyed()) { + return; + } + mDelegation.setFavDrawable(isFav ? R.drawable.ic_faved : R.drawable.ic_fav); + } + + @Override + public void showFavError() { + + } + + @Override + public void showNetworkError(int strId) { + if (isDestroy()) + return; + dismissLoadingDialog(); + SimplexToast.show(this, "评论失败"); + } + + @Override + public void showCommentSuccess(Comment comment) { + if (isDestroy()) + return; + if (mDelegation == null) + return; + mCommentId = 0; + mCommentAuthorId = 0; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(true); + AppContext.showToastShort(R.string.pub_comment_success); + mDelegation.getCommentText().setHint(mCommentHint); + mDelegation.getBottomSheet().getEditText().setText(""); + mDelegation.getBottomSheet().getEditText().setHint(mCommentHint); + mDelegation.getBottomSheet().dismiss(); + dismissLoadingDialog(); + SimplexToast.show(this, "评论成功"); + } + + @Override + public void showCommentError(String message) { + if (isDestroy()) + return; + dismissLoadingDialog(); + SimplexToast.show(this, "评论失败"); + } + + @SuppressLint("SetTextI18n") + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu_web_artivle, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + ClipManager.IS_SYSTEM_URL = true; + mShareDialog.show(); + break; + case R.id.menu_report: + if (!AccountHelper.isLogin()) { + LoginActivity.show(this); + return false; + } + if (mArticle != null) { + ReportDialog.create(this, 0, TypeFormat.formatUrl(mArticle), Report.TYPE_ARTICLE, mArticle.getKey()).show(); + } + break; + } + return false; + } + + protected void handleKeyDel() { + if (mCommentId != 0) { + if (TextUtils.isEmpty(mDelegation.getBottomSheet().getCommentText())) { + if (mInputDoubleEmpty) { + mCommentId = 0; + mCommentAuthorId = 0; + mDelegation.setCommentHint(mCommentHint); + mDelegation.getBottomSheet().getEditText().setHint(mCommentHint); + } else { + mInputDoubleEmpty = true; + } + } else { + mInputDoubleEmpty = false; + } + } + } + + @Override + public void setPresenter(ArticleWebContract.Presenter presenter) { + // TODO: 2017/10/30 + } + + @Override + protected void onStop() { + super.onStop(); + ClipManager.IS_SYSTEM_URL = false; + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + + } + + @Override + protected void onDestroy() { + super.onDestroy(); + ClipManager.IS_SYSTEM_URL = false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/web/ArticleWebContract.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/web/ArticleWebContract.java new file mode 100644 index 0000000000000000000000000000000000000000..1b8fce21cd89314833de8598701352be67dd2b1f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/web/ArticleWebContract.java @@ -0,0 +1,29 @@ +package net.oschina.app.improve.main.synthesize.web; + +import net.oschina.app.improve.base.BasePresenter; +import net.oschina.app.improve.base.BaseView; +import net.oschina.app.improve.bean.comment.Comment; + +/** + * 头条浏览器 + * Created by huanghaibin on 2017/10/30. + */ + +interface ArticleWebContract { + + interface View extends BaseView { + void showCommentSuccess(Comment comment); + + void showCommentError(String message); + + void showFavReverseSuccess(boolean isFav); + + void showFavError(); + } + + interface Presenter extends BasePresenter { + void putArticleComment(String content, long referId, long reAuthorId); + + void fav(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/web/ArticleWebPresenter.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/web/ArticleWebPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..750ee91c682792596d3bea1961aac6002bdd7018 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/web/ArticleWebPresenter.java @@ -0,0 +1,99 @@ +package net.oschina.app.improve.main.synthesize.web; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.Collection; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.comment.Comment; + +import java.lang.reflect.Type; + +import cz.msebera.android.httpclient.Header; + +/** + * 头条浏览器 + * Created by huanghaibin on 2017/10/30. + */ + +class ArticleWebPresenter implements ArticleWebContract.Presenter { + private final ArticleWebContract.View mView; + private final Article mArticle; + ArticleWebPresenter(ArticleWebContract.View mView, Article article) { + this.mView = mView; + this.mArticle = article; + this.mView.setPresenter(this); + } + + @Override + public void putArticleComment(String content, long referId, long reAuthorId) { + OSChinaApi.pubArticleComment(mArticle.getKey(), + content, + referId, + reAuthorId, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.tip_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + Comment respComment = resultBean.getResult(); + if (respComment != null) { + mView.showCommentSuccess(respComment); + } + } else { + mView.showCommentError(resultBean.getMessage()); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + mView.showCommentError("评论失败"); + } + } + }); + } + + + @Override + public void fav() { + OSChinaApi.articleFav(new Gson().toJson(mArticle), + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showFavError(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean != null && resultBean.isSuccess()) { + Collection collection = resultBean.getResult(); + mArticle.setFavorite(collection.isFavorite()); + mView.showFavReverseSuccess(collection.isFavorite()); + } else { + mView.showFavError(); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/web/Rule.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/web/Rule.java new file mode 100644 index 0000000000000000000000000000000000000000..68b26fff0c523192f20d19dc6e541fad1a4ad68a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/web/Rule.java @@ -0,0 +1,29 @@ +package net.oschina.app.improve.main.synthesize.web; + +import java.io.Serializable; + +/** + * 广告过滤规则 + * Created by huanghaibin on 2018/1/18. + */ +@SuppressWarnings("all") +public class Rule implements Serializable{ + private String[] removeRules; + private String[] expandRules; + + public String[] getRemoveRules() { + return removeRules; + } + + public void setRemoveRules(String[] removeRules) { + this.removeRules = removeRules; + } + + public String[] getExpandRules() { + return expandRules; + } + + public void setExpandRules(String[] expandRules) { + this.expandRules = expandRules; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/web/WebActivity.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/web/WebActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..4c24e630bddaa9d5004fb30b00a08cb1923285eb --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/web/WebActivity.java @@ -0,0 +1,243 @@ +package net.oschina.app.improve.main.synthesize.web; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.widget.Toolbar; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.ProgressBar; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.main.MainActivity; +import net.oschina.app.improve.share.ShareDialog; +import net.oschina.app.improve.widget.OSCWebView; +import net.oschina.app.util.TDevice; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * WebView + * Created by huanghaibin on 2017/10/27. + */ + +public class WebActivity extends BackActivity implements OSCWebView.OnFinishListener { + + protected OSCWebView mWebView; + @Bind(R.id.progressBar) + ProgressBar mProgressBar; + @Bind(R.id.ll_root) + LinearLayout mLinearRoot; + private String mTitle; + protected ShareDialog mShareDialog; + private String mUrl; + private boolean isShowAd; + private boolean isWebViewFinish; + + public static void show(Context context, String url) { + if (!TDevice.hasWebView(context)) + return; + if (TextUtils.isEmpty(url)) + return; + Intent intent = new Intent(context, WebActivity.class); + intent.putExtra("url", url); + context.startActivity(intent); + } + + public static void show(Context context, String url, boolean isShowAd) { + if (TextUtils.isEmpty(url)) + return; + Intent intent = new Intent(context, WebActivity.class); + intent.putExtra("url", url); + intent.putExtra("isShowAd", isShowAd); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_web; + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void initWidget() { + super.initWidget(); + + mWebView = new OSCWebView(this); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0); + params.weight = 1; + mWebView.setVisibility(View.INVISIBLE); + mWebView.setLayoutParams(params); + mLinearRoot.addView(mWebView); + mWebView.setOnFinishFinish(this); + + mUrl = getIntent().getStringExtra("url"); + isShowAd = getIntent().getBooleanExtra("isShowAd", false); + setStatusBarDarkMode(); + setSwipeBackEnable(true); + mToolBar = (Toolbar) findViewById(R.id.toolbar); + if (mToolBar != null) { + setSupportActionBar(mToolBar); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setHomeButtonEnabled(false); + } + mToolBar.setTitleTextColor(Color.BLACK); + DrawableCompat.setTint(mToolBar.getNavigationIcon(), Color.BLACK); + } + mShareDialog = new ShareDialog(this); + + if (!TextUtils.isEmpty(mUrl)) + getRule(mUrl); + } + + protected void getRule(final String url) { + OSChinaApi.getWebRule(url, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (isDestroyed()) + return; + mWebView.loadUrl(url); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (isDestroyed()) + return; + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + mWebView.setRule(bean.getResult()); + mWebView.loadUrl(url); + } else { + mWebView.loadUrl(url); + } + } catch (Exception e) { + e.printStackTrace(); + mWebView.loadUrl(url); + } + } + }); + } + + + @SuppressLint("SetTextI18n") + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu_web, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + if (!TextUtils.isEmpty(mTitle) && !TextUtils.isEmpty(mUrl)) { + mShareDialog.init(this, mWebView.getTitle(), + " ", + mWebView.getUrl()); + mShareDialog.show(); + } + break; + } + return false; + } + + @Override + public void onReceivedTitle(String title) { + if (isDestroyed()) + return; + mShareDialog.setTitle(title); + mTitle = title; + mShareDialog.init(this, title, "", mWebView.getUrl()); + mToolBar.setTitle("返回"); + } + + @Override + public void onProgressChange(int progress) { + if (isDestroyed()) + return; + mProgressBar.setProgress(progress); + if(!mWebView.hasRule()){ + mWebView.setVisibility(View.VISIBLE); + return; + } + if (progress >= 60 && !isWebViewFinish) { + isWebViewFinish = true; + mWebView.postDelayed(new Runnable() { + @Override + public void run() { + if (isDestroyed()) + return; + mWebView.setVisibility(View.VISIBLE); + } + }, 800); + } + } + + @Override + public void onError() { + + } + + @Override + public void onFinish() { + if (isDestroyed()) + return; + mProgressBar.setVisibility(View.INVISIBLE); + } + + + @Override + public void finish() { + super.finish(); + if (isShowAd) { + MainActivity.show(this); + } + } + + @Override + public void onBackPressed() { + if (mWebView.canGoBack()) { + mWebView.goBack(); + } else { + if(!MainActivity.IS_SHOW){ + MainActivity.show(this); + } + super.onBackPressed(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mWebView != null) { + mWebView.onDestroy(); + } + if(!MainActivity.IS_SHOW){ + MainActivity.show(this); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/synthesize/web/ZBWebActivity.java b/app/src/main/java/net/oschina/app/improve/main/synthesize/web/ZBWebActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..af3ee8536814e7ca17f3d255f6e3a23ecfd31e2a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/synthesize/web/ZBWebActivity.java @@ -0,0 +1,237 @@ +package net.oschina.app.improve.main.synthesize.web; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.widget.Toolbar; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.ProgressBar; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.main.MainActivity; +import net.oschina.app.improve.share.ShareDialog; +import net.oschina.app.improve.widget.OSCWebView; +import net.oschina.app.util.TDevice; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * WebView + * Created by huanghaibin on 2017/10/27. + */ + +public class ZBWebActivity extends BackActivity implements OSCWebView.OnFinishListener { + + protected OSCWebView mWebView; + @Bind(R.id.progressBar) + ProgressBar mProgressBar; + @Bind(R.id.ll_root) + LinearLayout mLinearRoot; + private String mTitle; + protected ShareDialog mShareDialog; + private String mUrl; + private boolean isShowAd; + private boolean isWebViewFinish; + + public static void show(Context context, String url) { + if (!TDevice.hasWebView(context)) + return; + if (TextUtils.isEmpty(url)) + return; + Intent intent = new Intent(context, ZBWebActivity.class); + intent.putExtra("url", url); + context.startActivity(intent); + } + + public static void show(Context context, String url, boolean isShowAd) { + if (TextUtils.isEmpty(url)) + return; + Intent intent = new Intent(context, ZBWebActivity.class); + intent.putExtra("url", url); + intent.putExtra("isShowAd", isShowAd); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_web; + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void initWidget() { + super.initWidget(); + + mWebView = new OSCWebView(this); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0); + params.weight = 1; + mWebView.setVisibility(View.INVISIBLE); + mWebView.setLayoutParams(params); + mLinearRoot.addView(mWebView); + mWebView.setOnFinishFinish(this); + + mUrl = getIntent().getStringExtra("url"); + isShowAd = getIntent().getBooleanExtra("isShowAd", false); + setStatusBarDarkMode(); + setSwipeBackEnable(true); + mToolBar = (Toolbar) findViewById(R.id.toolbar); + if (mToolBar != null) { + setSupportActionBar(mToolBar); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setHomeButtonEnabled(false); + } + mToolBar.setTitleTextColor(Color.BLACK); + DrawableCompat.setTint(mToolBar.getNavigationIcon(), Color.BLACK); + } + mShareDialog = new ShareDialog(this); + if (!TextUtils.isEmpty(mUrl)) + getRule(mUrl); + } + + protected void getRule(final String url) { + OSChinaApi.getWebRule(url, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (isDestroyed()) + return; + mWebView.loadUrl(url); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (isDestroyed()) + return; + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + mWebView.setRule(bean.getResult()); + mWebView.loadUrl(url); + } else { + mWebView.loadUrl(url); + } + } catch (Exception e) { + e.printStackTrace(); + mWebView.loadUrl(url); + } + } + }); + } + + + @SuppressLint("SetTextI18n") + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu_web, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + if (!TextUtils.isEmpty(mTitle) && !TextUtils.isEmpty(mUrl)) { + mShareDialog.init(this, mWebView.getTitle(), + " ", + mWebView.getUrl()); + mShareDialog.setThumbBitmap(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_share_zb)); + mShareDialog.show(); + } + break; + } + return false; + } + + @Override + public void onReceivedTitle(String title) { + if (isDestroyed()) + return; + mShareDialog.setTitle(title); + mTitle = title; + mShareDialog.init(this, title, " ", mWebView.getUrl()); + } + + @Override + public void onProgressChange(int progress) { + if (isDestroyed()) + return; + mProgressBar.setProgress(progress); + if(!mWebView.hasRule()){ + mWebView.setVisibility(View.VISIBLE); + return; + } + if (progress >= 60 && !isWebViewFinish) { + isWebViewFinish = true; + mWebView.postDelayed(new Runnable() { + @Override + public void run() { + if (isDestroyed()) + return; + mWebView.setVisibility(View.VISIBLE); + } + }, 800); + } + } + + @Override + public void onError() { + + } + + @Override + public void onFinish() { + if (isDestroyed()) + return; + mProgressBar.setVisibility(View.INVISIBLE); + } + + + @Override + public void finish() { + super.finish(); + if (isShowAd) { + MainActivity.show(this); + } + } + + @Override + public void onBackPressed() { + if (mWebView.canGoBack()) { + mWebView.goBack(); + } else { + super.onBackPressed(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mWebView != null) { + mWebView.onDestroy(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tabs/DynamicTabFragment.java b/app/src/main/java/net/oschina/app/improve/main/tabs/DynamicTabFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..a343e1a7215226af94e9b56b5076bbbd2bf1dfde --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tabs/DynamicTabFragment.java @@ -0,0 +1,350 @@ +package net.oschina.app.improve.main.tabs; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Context; +import android.os.Handler; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; +import android.widget.ImageView; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.fragments.BaseTitleFragment; +import net.oschina.app.improve.bean.SubTab; +import net.oschina.app.improve.main.MainActivity; +import net.oschina.app.improve.main.subscription.SubFragment; +import net.oschina.app.improve.search.v2.SearchActivity; +import net.oschina.app.improve.widget.FragmentPagerAdapter; +import net.oschina.app.improve.widget.TabPickerView; +import net.oschina.app.interf.OnTabReselectListener; +import net.oschina.app.util.TDevice; +import net.oschina.common.utils.StreamUtil; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 动态栏目Fragment + * Created by thanatosx on 16/10/26. + */ + +public class DynamicTabFragment extends BaseTitleFragment implements OnTabReselectListener { + + @Bind(R.id.layout_tab) + TabLayout mLayoutTab; + @Bind(R.id.view_tab_picker) + TabPickerView mViewTabPicker; + @Bind(R.id.view_pager) + ViewPager mViewPager; + @Bind(R.id.iv_arrow_down) + ImageView mViewArrowDown; + + private MainActivity activity; + private Fragment mCurFragment; + private FragmentPagerAdapter mAdapter; + private static TabPickerView.TabPickerDataManager mTabPickerDataManager; + List tabs; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + activity = (MainActivity) context; + activity.addOnTurnBackListener(new MainActivity.TurnBackListener() { + @Override + public boolean onTurnBack() { + return mViewTabPicker != null && mViewTabPicker.onTurnBack(); + } + }); + } + + public static TabPickerView.TabPickerDataManager initTabPickerManager() { + if (mTabPickerDataManager == null) { + mTabPickerDataManager = new TabPickerView.TabPickerDataManager() { + @Override + public List setupActiveDataSet() { + FileReader reader = null; + try { + File file = AppContext.getInstance().getFileStreamPath("sub_tab_active.json"); + if (!file.exists()) return null; + reader = new FileReader(file); + return AppOperator.getGson().fromJson(reader, + new TypeToken>() { + }.getType()); + } catch (IOException e) { + e.printStackTrace(); + } finally { + StreamUtil.close(reader); + } + return null; + } + + @Override + public List setupOriginalDataSet() { + InputStreamReader reader = null; + try { + reader = new InputStreamReader( + AppContext.getInstance().getAssets().open("sub_tab_original.json") + , "UTF-8"); + return AppOperator.getGson().>fromJson(reader, + new TypeToken>() { + }.getType()); + } catch (IOException e) { + e.printStackTrace(); + } finally { + StreamUtil.close(reader); + } + return null; + } + + @Override + public void restoreActiveDataSet(List mActiveDataSet) { + OutputStreamWriter writer = null; + try { + writer = new OutputStreamWriter( + AppContext.getInstance().openFileOutput( + "sub_tab_active.json", Context.MODE_PRIVATE) + , "UTF-8"); + AppOperator.getGson().toJson(mActiveDataSet, writer); + AppContext.set("TabsMask", TDevice.getVersionCode()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + StreamUtil.close(writer); + } + } + }; + } + return mTabPickerDataManager; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + + mViewTabPicker.setTabPickerManager(initTabPickerManager()); + mViewTabPicker.setOnTabPickingListener(new TabPickerView.OnTabPickingListener() { + + private boolean isChangeIndex = false; + + @Override + @SuppressWarnings("all") + public void onSelected(final int position) { + final int index = mViewPager.getCurrentItem(); + mViewPager.setCurrentItem(position); + if (position == index) { + mAdapter.commitUpdate(); + // notifyDataSetChanged为什么会导致TabLayout位置偏移,而且需要延迟设置才能起效??? + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + mLayoutTab.getTabAt(position).select(); + } + }, 50); + } + } + + @Override + public void onRemove(int position, SubTab tab) { + isChangeIndex = true; + } + + @Override + public void onInsert(SubTab tab) { + isChangeIndex = true; + } + + @Override + public void onMove(int op, int np) { + isChangeIndex = true; + } + + @Override + public void onRestore(final List mActiveDataSet) { + if (!isChangeIndex) return; + AppOperator.getExecutor().execute(new Runnable() { + @Override + public void run() { + OutputStreamWriter writer = null; + try { + writer = new OutputStreamWriter( + AppContext.getInstance().openFileOutput( + "sub_tab_active.json", Context.MODE_PRIVATE) + , "UTF-8"); + AppOperator.getGson().toJson(mActiveDataSet, writer); + } catch (Exception e) { + e.printStackTrace(); + } finally { + StreamUtil.close(writer); + } + + /*String json = AppOperator.getGson().toJson(mActiveDataSet); + FileOutputStream fos = null; + try { + fos = AppContext.getInstance().openFileOutput("sub_tab_active.json", + Context.MODE_PRIVATE); + fos.write(json.getBytes("UTF-8")); + fos.flush(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + StreamUtil.close(fos); + }*/ + } + }); + isChangeIndex = false; + tabs.clear(); + tabs.addAll(mActiveDataSet); + mAdapter.notifyDataSetChanged(); + } + }); + + mViewTabPicker.setOnShowAnimation(new TabPickerView.Action1() { + @Override + public void call(ViewPropertyAnimator animator) { + mViewArrowDown.setEnabled(false); + activity.toggleNavTabView(false); + mViewArrowDown.animate() + .rotation(225) + .setDuration(380) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + super.onAnimationEnd(animator); + mViewArrowDown.setRotation(45); + mViewArrowDown.setEnabled(true); + } + }).start(); + + } + }); + + mViewTabPicker.setOnHideAnimator(new TabPickerView.Action1() { + @Override + public void call(ViewPropertyAnimator animator) { + mViewArrowDown.setEnabled(false); + activity.toggleNavTabView(true); + mViewArrowDown.animate() + .rotation(-180) + .setDuration(380) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + super.onAnimationEnd(animator); + mViewArrowDown.setRotation(0); + mViewArrowDown.setEnabled(true); + } + }); + } + }); + + tabs = new ArrayList<>(); + tabs.addAll(mViewTabPicker.getTabPickerManager().getActiveDataSet()); + for (SubTab tab : tabs) { + mLayoutTab.addTab(mLayoutTab.newTab().setText(tab.getName())); + } + + mViewPager.setAdapter(mAdapter = new FragmentPagerAdapter(getChildFragmentManager()) { + @Override + public Fragment getItem(int position) { + return SubFragment.newInstance(tabs.get(position)); + } + + @Override + public int getCount() { + return tabs.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + return tabs.get(position).getName(); + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + super.setPrimaryItem(container, position, object); + if (mCurFragment == null) { + commitUpdate(); + } + mCurFragment = (Fragment) object; + } + + //this is called when notifyDataSetChanged() is called + @Override + public int getItemPosition(Object object) { + return PagerAdapter.POSITION_NONE; + } + + }); + mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageScrollStateChanged(int state) { + super.onPageScrollStateChanged(state); + if (state == ViewPager.SCROLL_STATE_IDLE) { + mAdapter.commitUpdate(); + } + } + }); + mLayoutTab.setupWithViewPager(mViewPager); + mLayoutTab.setSmoothScrollingEnabled(true); + } + + @Override + protected int getContentLayoutId() { + return R.layout.fragment_dynamic_tab; + } + + @Override + protected int getTitleRes() { + return R.string.main_tab_name_news; + } + + @OnClick(R.id.iv_arrow_down) + void onClickArrow() { + if (mViewArrowDown.getRotation() != 0) { + mViewTabPicker.onTurnBack(); + } else { + mViewTabPicker.show(mLayoutTab.getSelectedTabPosition()); + } + } + + @Override + protected int getIconRes() { + return R.mipmap.btn_search_normal; + } + + @Override + protected View.OnClickListener getIconClickListener() { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + //SearchActivity.show(getContext()); + SearchActivity.show(getContext()); + } + }; + } + + + @Override + public void onTabReselect() { + if (mCurFragment != null && mCurFragment instanceof OnTabReselectListener) { + ((OnTabReselectListener) mCurFragment).onTabReselect(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tabs/ExploreFragment.java b/app/src/main/java/net/oschina/app/improve/main/tabs/ExploreFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..8f04649b3da4cb7d7be40bb2ac71e35357b2ad97 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tabs/ExploreFragment.java @@ -0,0 +1,149 @@ +package net.oschina.app.improve.main.tabs; + +import android.os.Bundle; +import android.view.View; +import android.widget.ImageView; + +import net.oschina.app.R; +import net.oschina.app.Setting; +import net.oschina.app.bean.SimpleBackPage; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.base.fragments.BaseTitleFragment; +import net.oschina.app.improve.bean.SubTab; +import net.oschina.app.improve.git.feature.FeatureActivity; +import net.oschina.app.improve.git.gist.GistActivity; +import net.oschina.app.improve.main.discover.ShakePresentActivity; +import net.oschina.app.improve.nearby.NearbyActivity; +import net.oschina.app.improve.search.v2.SearchActivity; +import net.oschina.app.interf.OnTabReselectListener; +import net.oschina.app.util.UIHelper; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * Created by fei on 2016/9/6. + * desc: + */ + +public class ExploreFragment extends BaseTitleFragment implements View.OnClickListener, OnTabReselectListener { + + @Bind(R.id.rl_soft) + View mRlActive; + + @Bind(R.id.rl_scan) + View mScan; + + @Bind(R.id.iv_has_location) + ImageView mIvLocated; + + @Override + protected int getIconRes() { + return R.mipmap.btn_search_normal; + } + + @Override + protected View.OnClickListener getIconClickListener() { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + //SearchActivity.show(getContext()); + SearchActivity.show(getContext()); + } + }; + } + + @Override + protected int getContentLayoutId() { + return R.layout.fragment_explore; + } + + @Override + protected int getTitleRes() { + return R.string.main_tab_name_explore; + } + + @Override + public void onResume() { + super.onResume(); + hasLocation(); + } + + @OnClick({R.id.rl_git, R.id.rl_gits, + R.id.rl_soft, R.id.rl_scan, + R.id.rl_shake, R.id.layout_events, + R.id.layout_nearby}) + @Override + public void onClick(View v) { + int id = v.getId(); + switch (id) { + case R.id.rl_git: + FeatureActivity.show(getActivity()); + break; + case R.id.rl_gits: + GistActivity.show(mContext); + break; + case R.id.rl_soft: //开源软件 + UIHelper.showSimpleBack(getActivity(), + SimpleBackPage.OPEN_SOURCE_SOFTWARE); + break; + case R.id.rl_scan: //扫一扫 + UIHelper.showScanActivity(getActivity()); + break; + case R.id.rl_shake: //摇一摇 + showShake(); + break; + case R.id.layout_events: //线下活动 + SubTab tab = new SubTab(); + + SubTab.Banner banner = tab.new Banner(); + banner.setCatalog(3); + banner.setHref("https://www.oschina.net/action/apiv2/banner?catalog=3"); + tab.setBanner(banner); + + tab.setName("线下活动"); + tab.setFixed(false); + tab.setHref("https://www.oschina.net/action/apiv2/sub_list?token=727d77c15b2ca641fff392b779658512"); + tab.setNeedLogin(false); + tab.setSubtype(1); + tab.setOrder(74); + tab.setToken("727d77c15b2ca641fff392b779658512"); + tab.setType(5); + + Bundle bundle = new Bundle(); + bundle.putSerializable("sub_tab", tab); + + UIHelper.showSimpleBack(getContext(), SimpleBackPage.OUTLINE_EVENTS, bundle); + break; + case R.id.layout_nearby: + if (!AccountHelper.isLogin()) { + LoginActivity.show(getContext()); + break; + } + //NearbyActivity.show(getContext()); + NearbyActivity.show(mContext); + break; + default: + break; + } + } + + @Override + public void onTabReselect() { + hasLocation(); + } + + private void showShake() { + ShakePresentActivity.show(getActivity()); + } + + private void hasLocation() { + boolean hasLocation = Setting.hasLocation(getContext()); + if (hasLocation) { + mIvLocated.setVisibility(View.VISIBLE); + } else { + mIvLocated.setVisibility(View.GONE); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tabs/TweetViewPagerFragment.java b/app/src/main/java/net/oschina/app/improve/main/tabs/TweetViewPagerFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..dacfb01e6af3d6269ab1bc926fbc0096453563ff --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tabs/TweetViewPagerFragment.java @@ -0,0 +1,110 @@ +package net.oschina.app.improve.main.tabs; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.View; + +import net.oschina.app.R; +import net.oschina.app.improve.base.fragments.BaseGeneralListFragment; +import net.oschina.app.improve.base.fragments.BaseGeneralRecyclerFragment; +import net.oschina.app.improve.base.fragments.BaseViewPagerFragment; +import net.oschina.app.improve.bean.SubTab; +import net.oschina.app.improve.main.subscription.SubFragment; +import net.oschina.app.improve.search.v2.SearchActivity; +import net.oschina.app.improve.tweet.fragments.TweetFragment; +import net.oschina.app.interf.OnTabReselectListener; + +/** + * Created by fei + * on 2016/9/5. + *

    + * Changed qiujuer + * on 2016/9/5. + */ +public class TweetViewPagerFragment extends BaseViewPagerFragment implements OnTabReselectListener { + + /** + * @param catalog {@link TweetFragment} + * @return Bundle + */ + private Bundle getBundle(int catalog) { + Bundle bundle = new Bundle(); + bundle.putInt(TweetFragment.BUNDLE_KEY_REQUEST_CATALOG, catalog); + return bundle; + } + + @Override + public void onTabReselect() { + + if (mBaseViewPager != null) { + BaseViewPagerAdapter pagerAdapter = (BaseViewPagerAdapter) mBaseViewPager.getAdapter(); + Fragment fragment = pagerAdapter.getCurFragment(); + if (fragment != null) { + if (fragment instanceof BaseGeneralListFragment) + ((BaseGeneralListFragment) fragment).onTabReselect(); + else if (fragment instanceof BaseGeneralRecyclerFragment) + ((BaseGeneralRecyclerFragment) fragment).onTabReselect(); + } + } + } + + @Override + protected PagerInfo[] getPagers() { + SubTab tab = new SubTab(); + tab.setType(3); + tab.setFixed(false); + tab.setName("每日乱弹"); + tab.setNeedLogin(false); + tab.setHref("https://www.oschina.net/action/apiv2/sub_list?token=263ee86f538884e70ee1ee50aed759b6"); + tab.setSubtype(5); + tab.setToken("263ee86f538884e70ee1ee50aed759b6"); + + Bundle bundle = new Bundle(); + bundle.putSerializable("sub_tab", tab); + + /*return new PagerInfo[]{ + new PagerInfo("最新动弹", TweetFragment.class, + getBundle(TweetFragment.CATALOG_NEW)), + new PagerInfo("好友动弹", TweetFragment.class, + getBundle(TweetFragment.CATALOG_FRIENDS)), + new PagerInfo("我的动弹", TweetFragment.class, + getBundle(TweetFragment.CATALOG_MYSELF)), + new PagerInfo("热门动弹", TweetFragment.class, + getBundle(TweetFragment.CATALOG_HOT)), + + };*/ + return new PagerInfo[]{ +// new PagerInfo("推荐话题", TopicTweetFragment.class, null), + new PagerInfo("最新动弹", TweetFragment.class, + getBundle(TweetFragment.CATALOG_NEW)), + new PagerInfo("热门动弹", TweetFragment.class, + getBundle(TweetFragment.CATALOG_HOT)), + new PagerInfo("每日乱弹", SubFragment.class, + bundle), + new PagerInfo("我的动弹", TweetFragment.class, + getBundle(TweetFragment.CATALOG_MYSELF)) + + }; + } + + @Override + protected int getTitleRes() { + return R.string.main_tab_name_tweet; + } + + @Override + protected int getIconRes() { + return R.mipmap.btn_search_normal; + } + + @Override + protected View.OnClickListener getIconClickListener() { + return new View.OnClickListener() { + @Override + public void onClick(View v) { + //SearchActivity.show(getContext()); + SearchActivity.show(getContext()); + } + }; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tags/SelectFieldActivity.java b/app/src/main/java/net/oschina/app/improve/main/tags/SelectFieldActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..878a86099b900c34c4d48bbed041dfc133b09be7 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tags/SelectFieldActivity.java @@ -0,0 +1,30 @@ +package net.oschina.app.improve.main.tags; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseActivity; + +/** + * 选择领域 + * Created by haibin on 2018/6/11. + */ + +public class SelectFieldActivity extends BaseActivity { + + public static void show(Context context) { + Intent intent = new Intent(context, SelectFieldActivity.class); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_select_field; + } + + @Override + public void onBackPressed() { + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tags/SelectIdentityActivity.java b/app/src/main/java/net/oschina/app/improve/main/tags/SelectIdentityActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..a2801633d6379acb4d6262745747e7d586fae8cf --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tags/SelectIdentityActivity.java @@ -0,0 +1,30 @@ +package net.oschina.app.improve.main.tags; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseActivity; + +/** + * 选择身份 + * Created by haibin on 2018/6/11. + */ + +public class SelectIdentityActivity extends BaseActivity { + + public static void show(Context context) { + Intent intent = new Intent(context, SelectIdentityActivity.class); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_select_identity; + } + + @Override + public void onBackPressed() { + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tags/SelectTagsActivity.java b/app/src/main/java/net/oschina/app/improve/main/tags/SelectTagsActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..3dc4ea36af93a920a33932bcd4dbbacb6a8f0c80 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tags/SelectTagsActivity.java @@ -0,0 +1,31 @@ +package net.oschina.app.improve.main.tags; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseActivity; + + +/** + * 选择标签界面 + * Created by haibin on 2018/6/11. + */ + +public class SelectTagsActivity extends BaseActivity { + + public static void show(Context context) { + Intent intent = new Intent(context, SelectTagsActivity.class); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_select_tags; + } + + @Override + public void onBackPressed() { + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/TweetPagerFragment.java b/app/src/main/java/net/oschina/app/improve/main/tweet/TweetPagerFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..c143b6eda2195dcd189291bd673cbf90e8ece0d6 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/TweetPagerFragment.java @@ -0,0 +1,94 @@ +package net.oschina.app.improve.main.tweet; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.View; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.base.fragments.BaseGeneralListFragment; +import net.oschina.app.improve.base.fragments.BaseGeneralRecyclerFragment; +import net.oschina.app.improve.base.fragments.BasePagerFragment; +import net.oschina.app.improve.bean.SubTab; +import net.oschina.app.improve.main.subscription.SubFragment; +import net.oschina.app.improve.tweet.fragments.TweetFragment; +import net.oschina.app.interf.OnTabReselectListener; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; + +/** + * 新版动弹界面 + * Created by huanghaibin on 2017/10/23. + */ + +public class TweetPagerFragment extends BasePagerFragment implements OnTabReselectListener { + + + @Bind(R.id.viewStatusBar) + View mStatusBar; + + public static TweetPagerFragment newInstance() { + return new TweetPagerFragment(); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_tweet_pager; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + //setStatusBarPadding(); + if (BaseActivity.hasSetStatusBarColor) { + mStatusBar.setBackgroundColor(getResources().getColor(R.color.status_bar_color)); + } + } + + @Override + public void onTabReselect() { + if (mViewPager != null && mViewPager.getAdapter() != null) { + BasePagerFragment.Adapter pagerAdapter = (BasePagerFragment.Adapter) mViewPager.getAdapter(); + Fragment fragment = pagerAdapter.getCurFragment(); + if (fragment != null) { + if (fragment instanceof BaseGeneralListFragment) + ((BaseGeneralListFragment) fragment).onTabReselect(); + else if (fragment instanceof BaseGeneralRecyclerFragment) + ((BaseGeneralRecyclerFragment) fragment).onTabReselect(); + } + } + } + + @Override + protected List getFragments() { + List fragments = new ArrayList<>(); + fragments.add(TweetFragment.newInstance(TweetFragment.CATALOG_NEW)); + fragments.add(TweetFragment.newInstance(TweetFragment.CATALOG_HOT)); + fragments.add(getSubFragment()); + fragments.add(TweetFragment.newInstance(TweetFragment.CATALOG_MYSELF)); + return fragments; + } + + @Override + protected String[] getTitles() { + return getResources().getStringArray(R.array.tweet_titles); + } + + private SubFragment getSubFragment() { + SubTab tab = new SubTab(); + tab.setType(3); + tab.setFixed(false); + tab.setName("每日乱弹"); + tab.setNeedLogin(false); + tab.setHref("https://www.oschina.net/action/apiv2/sub_list?token=263ee86f538884e70ee1ee50aed759b6"); + tab.setSubtype(5); + tab.setToken("263ee86f538884e70ee1ee50aed759b6"); + + Bundle bundle = new Bundle(); + bundle.putSerializable("sub_tab", tab); + return SubFragment.newInstance(tab); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/comment/TweetCommentContract.java b/app/src/main/java/net/oschina/app/improve/main/tweet/comment/TweetCommentContract.java new file mode 100644 index 0000000000000000000000000000000000000000..377beb24333870650d6fa7cda4cf151db0503526 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/comment/TweetCommentContract.java @@ -0,0 +1,25 @@ +package net.oschina.app.improve.main.tweet.comment; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.bean.simple.TweetComment; + +/** + * 动弹评论列表 + * Created by huanghaibin on 2017/12/18. + */ + + interface TweetCommentContract { + + interface View extends BaseListView{ + void onRequestSuccess(); + + void showDeleteSuccess(int position); + + void showDeleteFailure(); + } + + interface Presenter extends BaseListPresenter{ + void deleteTweetComment(long id,TweetComment comment,int position); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/comment/TweetCommentFragment.java b/app/src/main/java/net/oschina/app/improve/main/tweet/comment/TweetCommentFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..f88d84785baa17850432f673b185b06ab5856d9b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/comment/TweetCommentFragment.java @@ -0,0 +1,138 @@ +package net.oschina.app.improve.main.tweet.comment; + +import android.app.ProgressDialog; +import android.content.Context; +import android.os.Bundle; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.simple.TweetComment; +import net.oschina.app.improve.tweet.adapter.TweetCommentAdapter; +import net.oschina.app.improve.tweet.contract.TweetDetailContract; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.utils.QuickOptionDialogHelper; +import net.oschina.app.util.HTMLUtil; + +import java.util.List; + +/** + * 动弹评论列表 + * Created by huanghaibin on 2017/12/18. + */ + +public class TweetCommentFragment extends BaseRecyclerFragment + implements TweetCommentContract.View, TweetDetailContract.ICmnView, BaseRecyclerAdapter.OnItemLongClickListener { + + private TweetDetailContract.Operator mOperator; + private TweetDetailContract.IAgencyView mAgencyView; + + private ProgressDialog dialog = null; + public static TweetCommentFragment newInstance(Tweet tweet) { + TweetCommentFragment fragment = new TweetCommentFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable("tweet", tweet); + fragment.setArguments(bundle); + return fragment; + } + + + public static TweetCommentFragment instantiate(TweetDetailContract.Operator operator, TweetDetailContract.IAgencyView mAgencyView) { + TweetCommentFragment fragment = new TweetCommentFragment(); + fragment.mOperator = operator; + fragment.mAgencyView = mAgencyView; + return fragment; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mOperator = (TweetDetailContract.Operator) context; + } + + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + } + + @Override + protected void initData() { + new TweetCommentPresenter(this, mOperator.getTweetDetail()); + super.initData(); + dialog = DialogHelper.getProgressDialog(getContext(), "正在删除……", false); + mAdapter.setOnItemLongClickListener(this); + } + + @Override + protected void onItemClick(TweetComment tweetComment, int position) { + + mOperator.toReply(tweetComment); + } + + @Override + public void onLongClick(final int position, long itemId) { + final TweetComment comment = mAdapter.getItem(position); + if (comment == null) return; + + QuickOptionDialogHelper.with(getContext()) + .addCopy(HTMLUtil.delHTMLTag(comment.getContent())) + .addOther(comment.getAuthor().getId() == AccountHelper.getUserId(), + R.string.delete, new Runnable() { + @Override + public void run() { + mPresenter.deleteTweetComment(mOperator.getTweetDetail().getId(), comment, position); + } + }).show(); + } + + @Override + public void showDeleteSuccess(int position) { + if (mContext == null) + return; + dialog.dismiss(); + mAdapter.removeItem(position); + int count = mOperator.getTweetDetail().getCommentCount() - 1; + mOperator.getTweetDetail().setCommentCount(count); + mAgencyView.resetCmnCount(count); + AppContext.showToastShort("删除成功"); + } + + @Override + public void showDeleteFailure() { + if (mContext == null) + return; + dialog.dismiss(); + AppContext.showToastShort("删除失败"); + } + + @Override + public void onCommentSuccess(TweetComment comment) { + if (mContext == null || mPresenter == null) { + return; + } + mRefreshLayout.setRefreshing(true); + mRefreshLayout.setOnLoading(true); + onRefreshing(); + } + + + @Override + public void onRequestSuccess() { + if (mAdapter.getCount() < 20 && mAgencyView != null) + mAgencyView.resetCmnCount(mAdapter.getCount()); + } + + @Override + public List getComments() { + return mAdapter.getItems(); + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new TweetCommentAdapter(mContext); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/comment/TweetCommentPresenter.java b/app/src/main/java/net/oschina/app/improve/main/tweet/comment/TweetCommentPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..11971d8fccc36167573c0de922ce13c0870318b7 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/comment/TweetCommentPresenter.java @@ -0,0 +1,142 @@ +package net.oschina.app.improve.main.tweet.comment; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.TweetComment; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * 动弹评论列表 + * Created by huanghaibin on 2017/12/18. + */ + +class TweetCommentPresenter implements TweetCommentContract.Presenter { + private final TweetCommentContract.View mView; + private final Tweet mTweet; + private String mNextToken; + + TweetCommentPresenter(TweetCommentContract.View mView, Tweet mTweet) { + this.mView = mView; + this.mTweet = mTweet; + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + if (mTweet == null) + return; + OSChinaApi.getTweetCommentList(mTweet.getId(), "", new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> resultBean = new Gson().fromJson(responseString, getType()); + if (resultBean != null && resultBean.isSuccess()) { + PageBean pageBean = resultBean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List list = pageBean.getItems(); + mView.onRefreshSuccess(list); + mView.onRequestSuccess(); + if (list.size() == 0) { + mView.showNotMore(); + } else { + mView.showNotMore(); + } + mView.onComplete(); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + if (mTweet == null) + return; + OSChinaApi.getTweetCommentList(mTweet.getId(), mNextToken, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> resultBean = new Gson().fromJson(responseString, getType()); + if (resultBean != null && resultBean.isSuccess()) { + PageBean pageBean = resultBean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List list = pageBean.getItems(); + mView.onLoadMoreSuccess(list); + mView.onRequestSuccess(); + if (list.size() == 0) { + mView.showNotMore(); + } else { + mView.showNotMore(); + } + mView.onComplete(); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + } + }); + } + + @Override + public void deleteTweetComment(long id, TweetComment comment, final int position) { + OSChinaApi.deleteTweetComment(id, comment.getId(), new TextHttpResponseHandler() { + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showDeleteFailure(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean result = new Gson().fromJson( + responseString, new TypeToken() { + }.getType()); + if (result.isSuccess()) { + mView.showDeleteSuccess(position); + } else { + mView.showDeleteFailure(); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showDeleteFailure(); + } + + } + }); + } + + private Type getType() { + return new TypeToken>>() { + }.getType(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/detail/TweetDetaiPresenter.java b/app/src/main/java/net/oschina/app/improve/main/tweet/detail/TweetDetaiPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..808de364157cf58622e13960fecbd61ad7f2bff5 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/detail/TweetDetaiPresenter.java @@ -0,0 +1,9 @@ +package net.oschina.app.improve.main.tweet.detail; + +/** + * 动弹详情 + * Created by huanghaibin on 2017/11/14. + */ + +public class TweetDetaiPresenter { +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/detail/TweetDetailActivity.java b/app/src/main/java/net/oschina/app/improve/main/tweet/detail/TweetDetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..858bb90d6939fdc64199dc10147d92b50dace94f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/detail/TweetDetailActivity.java @@ -0,0 +1,43 @@ +package net.oschina.app.improve.main.tweet.detail; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.Tweet; + +/** + * 动弹详情 + * Created by huanghaibin on 2017/11/14. + */ + +public class TweetDetailActivity extends BackActivity{ + + public static void show(Context context, Tweet tweet) { + Intent intent = new Intent(context, TweetDetailActivity.class); + intent.putExtra("tweet", tweet); + context.startActivity(intent); + } + + public static void show(Context context, long id) { + Tweet tweet = new Tweet(); + tweet.setId(id); + show(context, tweet); + } + + @Override + protected int getContentView() { + return R.layout.activity_tweet_detail_v2; + } + + @Override + protected void initWidget() { + super.initWidget(); + } + + @Override + protected void initData() { + super.initData(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/detail/TweetDetailContract.java b/app/src/main/java/net/oschina/app/improve/main/tweet/detail/TweetDetailContract.java new file mode 100644 index 0000000000000000000000000000000000000000..dee54d9e31356db23ef57e0369e57de96f0f444e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/detail/TweetDetailContract.java @@ -0,0 +1,9 @@ +package net.oschina.app.improve.main.tweet.detail; + +/** + * 动弹详情 + * Created by huanghaibin on 2017/11/14. + */ + +public interface TweetDetailContract { +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/emoji/EmojiFragment.java b/app/src/main/java/net/oschina/app/improve/main/tweet/emoji/EmojiFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..a14cc9366557d91f4489281e044ac404bbf03e5a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/emoji/EmojiFragment.java @@ -0,0 +1,38 @@ +package net.oschina.app.improve.main.tweet.emoji; + +import net.oschina.app.R; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.face.FacePanelView; +import net.oschina.app.improve.media.Util; +import net.oschina.app.improve.widget.Keyboard; + +import butterknife.Bind; + +/** + * 新版本emoji界面 + * Created by huanghaibin on 2018/1/3. + */ + +public class EmojiFragment extends BaseFragment { + @Bind(R.id.faceView) + FacePanelView mFaceView; + + public static EmojiFragment newInstance() { + return new EmojiFragment(); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_emoji; + } + + @Override + public void onResume() { + super.onResume(); + if (Keyboard.KEYBOARD_HEIGHT >= 300) { + mRoot.getLayoutParams().height = Keyboard.KEYBOARD_HEIGHT; + } else { + mRoot.getLayoutParams().height = Util.dipTopx(mContext, 230); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/list/TweetAdapter.java b/app/src/main/java/net/oschina/app/improve/main/tweet/list/TweetAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..ba29cb14bbe88d7e8d2dfcd7b7659b7c0a06946d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/list/TweetAdapter.java @@ -0,0 +1,313 @@ +package net.oschina.app.improve.main.tweet.list; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.TweetLikeReverse; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.utils.Platform; +import net.oschina.app.improve.utils.parser.TweetParser; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.improve.widget.TweetPicturesLayout; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.UIHelper; +import net.oschina.app.widget.TweetTextView; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import butterknife.ButterKnife; +import cz.msebera.android.httpclient.Header; + +/** + * 动弹列表 + * Created by huanghaibin on 2017/11/14. + */ +@SuppressWarnings("unused") +public class TweetAdapter extends BaseRecyclerAdapter implements View.OnClickListener{ + private View.OnClickListener mOnLikeClickListener; + private boolean isShowIdentityView; + + public TweetAdapter(Context context) { + super(context, ONLY_FOOTER); + } + + public void setShowIdentityView(boolean showIdentityView) { + isShowIdentityView = showIdentityView; + } + + private void initListener() { + mOnLikeClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(mContext); + return; + } + final int position = Integer.valueOf(v.getTag().toString()); + Tweet tweet = getItem(position); + if (tweet == null) return; + OSChinaApi.reverseTweetLike(tweet.getId(), new TweetLikedHandler(position)); + } + }; + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new TweetViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_list_tweet_improve, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder h, final Tweet item, int position) { + TweetViewHolder holder = (TweetViewHolder) h; + + final Author author = item.getAuthor(); + + if (author == null) { + holder.mViewPortrait.setup(0, "匿名用户", ""); + holder.mViewName.setText("匿名用户"); + } else { + holder.mViewPortrait.setup(author); + holder.mViewPortrait.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + OtherUserHomeActivity.show(mContext, author); + } + }); + + holder.mViewName.setText(author.getName()); + } + if (isShowIdentityView) { + holder.mIdentityView.setVisibility(View.VISIBLE); + holder.mIdentityView.setup(author); + } else { + holder.mIdentityView.setVisibility(View.GONE); + } + + + holder.mViewTime.setText(StringUtils.formatSomeAgo(item.getPubDate())); + holder.mViewPlatform.setText(String.format("来自 %s", Platform.getPlatform(item.getAppClient()))); + holder.mViewPlatform.setVisibility(TextUtils.isEmpty(Platform.getPlatform(item.getAppClient())) ? View.GONE : View.VISIBLE); + if (!TextUtils.isEmpty(item.getContent())) { + String content = item.getContent().replaceAll("[\n\\s]+", " "); + //holder.mViewContent.setText(AssimilateUtils.assimilate(mContext, content)); + holder.mViewContent.setText(TweetParser.getInstance().parse(mContext, content)); + holder.mViewContent.setMovementMethod(LinkMovementMethod.getInstance()); + holder.mViewContent.setFocusable(false); + holder.mViewContent.setDispatchToParent(true); + holder.mViewContent.setLongClickable(false); + } + + /* - @hide - */ + /*if (item.getAudio() != null) { + if (mRecordBitmap == null) { + initRecordImg(mContext); + } + ImageSpan recordImg = new ImageSpan(mContext, mRecordBitmap); + SpannableString str = new SpannableString("c"); + str.setSpan(recordImg, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + holder.mViewContent.setText(str); + holder.mViewContent.append(spannable); + } else { + holder.mViewContent.setText(spannable); + }*/ + + holder.mViewLikeState.setImageResource( + item.isLiked() + ? R.mipmap.ic_thumbup_actived + : R.mipmap.ic_thumb_normal); + holder.mLinearLike.setTag(position); + holder.mLinearLike.setOnClickListener(mOnLikeClickListener); + + Tweet.Image[] images = item.getImages(); + holder.mLayoutFlow.setImage(images); + + /* - statistics - */ + if (item.getStatistics() != null) { + holder.mViewLikeCount.setText(String.valueOf(item.getStatistics().getLike())); + holder.mViewCmmCount.setText(String.valueOf(item.getStatistics().getComment())); + int mDispatchCount = item.getStatistics().getTransmit(); + if (mDispatchCount <= 0) { + //holder.mViewDispatchCount.setVisibility(View.GONE); + holder.mViewDispatchCount.setText("转发"); + } else { + holder.mViewDispatchCount.setVisibility(View.VISIBLE); + holder.mViewDispatchCount.setText(String.valueOf(item.getStatistics().getTransmit())); + } + } else { + holder.mViewLikeCount.setText(String.valueOf(item.getLikeCount())); + holder.mViewCmmCount.setText(String.valueOf(item.getCommentCount())); + holder.mViewDispatchCount.setVisibility(View.GONE); + } + String textCount = holder.mViewLikeCount.getText().toString(); + holder.mViewLikeCount.setText("0".equals(textCount) ? "赞" : textCount); + + String textComCount = holder.mViewCmmCount.getText().toString(); + holder.mViewCmmCount.setText("0".equals(textComCount) ? "评论" : textComCount); + + /* - about - */ + if (item.getAbout() != null) { + holder.mLayoutRef.setVisibility(View.VISIBLE); + holder.mLayoutRef.setTag(position); + holder.mLayoutRef.setOnClickListener(this); + + About about = item.getAbout(); + holder.mLayoutRefImages.setImage(about.getImages()); + + if (!About.check(about)) { + holder.mViewRefTitle.setVisibility(View.VISIBLE); + holder.mViewRefTitle.setText("不存在或已删除的内容"); + holder.mViewRefContent.setText("抱歉,该内容不存在或已被删除"); + } else { + if (about.getType() == OSChinaApi.COMMENT_TWEET) { + holder.mViewRefTitle.setVisibility(View.GONE); + String aname = "@" + about.getTitle(); + String cnt = about.getContent(); + //Spannable spannable = AssimilateUtils.assimilate(mContext, cnt); + Spannable spannable = TweetParser.getInstance().parse(mContext, cnt); + SpannableStringBuilder builder = new SpannableStringBuilder(); + builder.append(aname).append(": "); + builder.append(spannable); + ForegroundColorSpan span = new ForegroundColorSpan( + mContext.getResources().getColor(R.color.day_colorPrimary)); + builder.setSpan(span, 0, aname.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + holder.mViewRefContent.setMaxLines(Integer.MAX_VALUE); + holder.mViewRefContent.setText(builder); + } else { + holder.mViewRefTitle.setVisibility(View.VISIBLE); + holder.mViewRefTitle.setText(about.getTitle()); + holder.mViewRefContent.setMaxLines(3); + holder.mViewRefContent.setEllipsize(TextUtils.TruncateAt.END); + holder.mViewRefContent.setText(about.getContent()); + } + } + } else { + holder.mLayoutRef.setVisibility(View.GONE); + } + } + + /** + * 点击引用时触发 + * + * @param v Ref View + */ + @Override + public void onClick(View v) { + int position = Integer.valueOf(v.getTag().toString()); + Tweet tweet = getItem(position); + if (tweet == null) return; + About about = tweet.getAbout(); + if (about == null) return; + UIHelper.showDetail(mContext, about.getType(), about.getId(), about.getHref()); + } + + /** + * Tweet Item View Holder + */ + public static class TweetViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.iv_tweet_face) + PortraitView mViewPortrait; + @Bind(R.id.identityView) + IdentityView mIdentityView; + @Bind(R.id.tv_tweet_name) + TextView mViewName; + @Bind(R.id.tv_tweet_time) + TextView mViewTime; + @Bind(R.id.tv_tweet_platform) + TextView mViewPlatform; + @Bind(R.id.tv_tweet_like_count) + TextView mViewLikeCount; + @Bind(R.id.tv_tweet_comment_count) + TextView mViewCmmCount; + @Bind(R.id.tweet_item) + TweetTextView mViewContent; + @Bind(R.id.iv_like_state) + ImageView mViewLikeState; + @Bind(R.id.fl_image) + TweetPicturesLayout mLayoutFlow; + @Bind(R.id.tv_ref_title) + TextView mViewRefTitle; + @Bind(R.id.tv_ref_content) + TextView mViewRefContent; + @Bind(R.id.layout_ref_images) + TweetPicturesLayout mLayoutRefImages; + @Bind(R.id.iv_dispatch) + ImageView mViewDispatch; + @Bind(R.id.tv_dispatch_count) + TextView mViewDispatchCount; + @Bind(R.id.layout_ref) + LinearLayout mLayoutRef; + @Bind(R.id.ll_like) + LinearLayout mLinearLike; + + + TweetViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } + + /** + * 点赞请求的回调 + */ + private class TweetLikedHandler extends TextHttpResponseHandler { + private int position; + + TweetLikedHandler(int position) { + this.position = position; + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + SimplexToast.show(mContext, "点赞操作失败"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + Tweet tweet = getItem(position); + if (tweet == null) return; + tweet.setLiked(resultBean.getResult().isLiked()); + tweet.setLikeCount(resultBean.getResult().getLikeCount()); + if (tweet.getStatistics() != null) { + tweet.getStatistics().setLike(resultBean.getResult().getLikeCount()); + } + updateItem(position); + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + } + + +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/list/TweetContract.java b/app/src/main/java/net/oschina/app/improve/main/tweet/list/TweetContract.java new file mode 100644 index 0000000000000000000000000000000000000000..9d45b6cc50f01a3211efab7aa6c59af6732146b6 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/list/TweetContract.java @@ -0,0 +1,9 @@ +package net.oschina.app.improve.main.tweet.list; + +/** + * 动弹列表 + * Created by huanghaibin on 2017/11/14. + */ + +public interface TweetContract { +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/list/TweetFragment.java b/app/src/main/java/net/oschina/app/improve/main/tweet/list/TweetFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..7c89a8a56974412c0cf696d4e72adb83ab9db2a3 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/list/TweetFragment.java @@ -0,0 +1,9 @@ +package net.oschina.app.improve.main.tweet.list; + +/** + * 动弹列表 + * Created by huanghaibin on 2017/11/14. + */ + +public class TweetFragment { +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/list/TweetPresenter.java b/app/src/main/java/net/oschina/app/improve/main/tweet/list/TweetPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..d13923112e47d971d7dcc4cc4f2fda69f2e867c2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/list/TweetPresenter.java @@ -0,0 +1,9 @@ +package net.oschina.app.improve.main.tweet.list; + +/** + * 动弹列表 + * Created by huanghaibin on 2017/11/14. + */ + +public class TweetPresenter { +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/praise/TweetPraiseContract.java b/app/src/main/java/net/oschina/app/improve/main/tweet/praise/TweetPraiseContract.java new file mode 100644 index 0000000000000000000000000000000000000000..63b4aae8a27f43e928d9f2464e02ece5eb11e02a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/praise/TweetPraiseContract.java @@ -0,0 +1,21 @@ +package net.oschina.app.improve.main.tweet.praise; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.bean.simple.TweetLike; + +/** + * 动弹点赞列表 + * Created by huanghaibin on 2017/12/18. + */ + + interface TweetPraiseContract { + + interface View extends BaseListView{ + + } + + interface Presenter extends BaseListPresenter{ + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/praise/TweetPraiseFragment.java b/app/src/main/java/net/oschina/app/improve/main/tweet/praise/TweetPraiseFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..d0bcfca4f50f725c8dae9cfdbb5db79aa2013b5b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/praise/TweetPraiseFragment.java @@ -0,0 +1,49 @@ +package net.oschina.app.improve.main.tweet.praise; + +import android.os.Bundle; + +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.simple.TweetLike; +import net.oschina.app.improve.tweet.adapter.TweetLikeUsersAdapter; + +/** + * 动弹点赞列表 + * Created by huanghaibin on 2017/12/18. + */ + +public class TweetPraiseFragment extends BaseRecyclerFragment + implements TweetPraiseContract.View { + private Tweet mTweet; + + public static TweetPraiseFragment newInstance(Tweet tweet) { + TweetPraiseFragment fragment = new TweetPraiseFragment(); + Bundle bundle = new Bundle(); + bundle.putSerializable("tweet", tweet); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + mTweet = (Tweet) bundle.getSerializable("tweet"); + } + + @Override + protected void initData() { + new TweetPraisePresenter(this, mTweet); + super.initData(); + } + + @Override + protected void onItemClick(TweetLike tweetLike, int position) { + + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new TweetLikeUsersAdapter(mContext); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/praise/TweetPraisePresenter.java b/app/src/main/java/net/oschina/app/improve/main/tweet/praise/TweetPraisePresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..c7c135158dd53bace105afd93a16992a1af79df5 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/praise/TweetPraisePresenter.java @@ -0,0 +1,107 @@ +package net.oschina.app.improve.main.tweet.praise; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.TweetLike; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * 动弹点赞列表 + * Created by huanghaibin on 2017/12/18. + */ + +class TweetPraisePresenter implements TweetPraiseContract.Presenter { + private final TweetPraiseContract.View mView; + private final Tweet mTweet; + private String mNextToken; + + TweetPraisePresenter(TweetPraiseContract.View mView, Tweet mTweet) { + this.mView = mView; + this.mTweet = mTweet; + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + OSChinaApi.getTweetCommentList(mTweet.getId(), "", new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> resultBean = new Gson().fromJson(responseString, getType()); + if (resultBean != null && resultBean.isSuccess()) { + PageBean pageBean = resultBean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List list = pageBean.getItems(); + mView.onRefreshSuccess(list); + if (list.size() == 0) { + mView.showNotMore(); + } else { + mView.showNotMore(); + } + mView.onComplete(); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + OSChinaApi.getTweetCommentList(mTweet.getId(), mNextToken, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> resultBean = new Gson().fromJson(responseString, getType()); + if (resultBean != null && resultBean.isSuccess()) { + PageBean pageBean = resultBean.getResult(); + mNextToken = pageBean.getNextPageToken(); + List list = pageBean.getItems(); + mView.onLoadMoreSuccess(list); + if (list.size() == 0) { + mView.showNotMore(); + } else { + mView.showNotMore(); + } + mView.onComplete(); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + } + }); + } + + private Type getType() { + return new TypeToken>>() { + }.getType(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/tweet/pub/PubTweetActivity.java b/app/src/main/java/net/oschina/app/improve/main/tweet/pub/PubTweetActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..a119cf4c2924fe2ff28ef144e6b6b7733444c800 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/tweet/pub/PubTweetActivity.java @@ -0,0 +1,23 @@ +package net.oschina.app.improve.main.tweet.pub; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.improve.base.activities.BackActivity; + +/** + * 新版本发布动弹界面 + * Created by huanghaibin on 2018/1/3. + */ + +public class PubTweetActivity extends BackActivity { + + public static void show(Context context) { + context.startActivity(new Intent(context, PubTweetActivity.class)); + } + + @Override + protected int getContentView() { + return 0; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/update/CheckUpdateManager.java b/app/src/main/java/net/oschina/app/improve/main/update/CheckUpdateManager.java new file mode 100644 index 0000000000000000000000000000000000000000..dc7af04a1c48e11a61b82478a78104fcaba18aed --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/update/CheckUpdateManager.java @@ -0,0 +1,110 @@ +package net.oschina.app.improve.main.update; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.BuildConfig; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.Version; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.utils.DialogHelper; + +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2016/10/19. + */ +public class CheckUpdateManager { + + + private ProgressDialog mWaitDialog; + private Context mContext; + private boolean mIsShowDialog; + + public CheckUpdateManager(Context context, boolean showWaitingDialog) { + this.mContext = context; + mIsShowDialog = showWaitingDialog; + if (mIsShowDialog) { + mWaitDialog = DialogHelper.getProgressDialog(mContext); + mWaitDialog.setMessage("正在检查中..."); + mWaitDialog.setCancelable(true); + mWaitDialog.setCanceledOnTouchOutside(true); + } + } + + + public void checkUpdate(final boolean isHasShow) { + if (mIsShowDialog) { + mWaitDialog.show(); + } + OSChinaApi.checkUpdate(new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + try { + if (mIsShowDialog) { + DialogHelper.getMessageDialog(mContext, "网络异常,无法获取新版本信息").show(); + } + if (mWaitDialog != null) { + mWaitDialog.dismiss(); + } + }catch (Exception e){ + e.printStackTrace(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> bean = AppOperator.createGson() + .fromJson(responseString, new TypeToken>>() { + }.getType()); + if (bean != null && bean.isSuccess()) { + List versions = bean.getResult(); + if (versions.size() > 0) { + final Version version = versions.get(0); + int curVersionCode = BuildConfig.VERSION_CODE; + int code = Integer.parseInt(version.getCode()); + if (curVersionCode < code) { + //是否弹出更新 + if (OSCSharedPreference.getInstance().isShowUpdate() || isHasShow) { + UpdateActivity.show((Activity) mContext, version); + } + } else { + if (mIsShowDialog) { + DialogHelper.getMessageDialog(mContext, "已经是新版本了").show(); + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onFinish() { + super.onFinish(); + if (mWaitDialog != null) { + mWaitDialog.dismiss(); + } + } + }); + } + + @SuppressWarnings("all") + public void setCaller(RequestPermissions caller) { + RequestPermissions mCaller = caller; + } + + public interface RequestPermissions { + void call(Version version); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/update/DownloadService.java b/app/src/main/java/net/oschina/app/improve/main/update/DownloadService.java new file mode 100644 index 0000000000000000000000000000000000000000..996c6c7d223f30404f585428d04b0442318c1710 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/update/DownloadService.java @@ -0,0 +1,214 @@ +package net.oschina.app.improve.main.update; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.support.annotation.Nullable; +import android.support.v4.content.FileProvider; +import android.widget.RemoteViews; + +import net.oschina.app.AppConfig; +import net.oschina.app.R; +import net.oschina.app.improve.main.MainActivity; +import net.oschina.common.utils.StreamUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * 下载服务 + * Created by haibin + * on 2016/10/19. + */ +@SuppressWarnings("all") +public class DownloadService extends Service { + public static boolean isDownload = false; + + private String mUrl; + private String mTitle = "正在下载"; + private String saveFileName = AppConfig.DEFAULT_SAVE_FILE_PATH; + + private NotificationManager mNotificationManager; + private Notification mNotification; + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case 0: + mNotificationManager.cancel(0); + installApk(); + break; + case 1: + int rate = msg.arg1; + if (rate < 100) { + RemoteViews views = mNotification.contentView; + views.setTextViewText(R.id.tv_download_progress, mTitle + "(" + rate + + "%" + ")"); + //views.setTextColor(R.id.tv_download_progress,getColor(R.color.text_title_color)); + views.setProgressBar(R.id.pb_progress, 100, rate, + false); + } else { + // 下载完毕后变换通知形式 + mNotification.flags = Notification.FLAG_AUTO_CANCEL; + mNotification.contentView = null; + Intent intent = new Intent(getApplicationContext(), MainActivity.class); + + intent.putExtra("completed", "yes"); + + PendingIntent contentIntent = PendingIntent.getActivity( + getApplicationContext(), 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } + mNotificationManager.notify(0, mNotification); + break; + } + + } + }; + + public static void startService(Context context, String downloadUrl) { + Intent intent = new Intent(context, DownloadService.class); + intent.putExtra("url", downloadUrl); + context.startService(intent); + } + + @Override + public void onCreate() { + super.onCreate(); + isDownload = true; + mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + mUrl = intent.getStringExtra("url"); + File file = new File(saveFileName); + if (!file.exists()) { + file.mkdirs(); + } + final File apkFile = new File(saveFileName + "osc.apk"); + setUpNotification(); + new Thread() { + @Override + public void run() { + try { + downloadUpdateFile(mUrl, apkFile); + } catch (Exception e) { + e.printStackTrace(); + } + } + }.start(); + return super.onStartCommand(intent, flags, startId); + } + + private long downloadUpdateFile(String downloadUrl, File saveFile) throws Exception { + int downloadCount = 0; + int currentSize = 0; + long totalSize = 0; + int updateTotalSize = 0; + + HttpURLConnection httpConnection = null; + InputStream is = null; + FileOutputStream fos = null; + + try { + URL url = new URL(downloadUrl); + httpConnection = (HttpURLConnection) url.openConnection(); + httpConnection.setConnectTimeout(10000); + httpConnection.setReadTimeout(20000); + updateTotalSize = httpConnection.getContentLength(); + if (httpConnection.getResponseCode() == 404) { + throw new Exception("fail!"); + } + is = httpConnection.getInputStream(); + fos = new FileOutputStream(saveFile, false); + byte buffer[] = new byte[2048]; + int readSize = 0; + while ((readSize = is.read(buffer)) > 0) { + + fos.write(buffer, 0, readSize); + totalSize += readSize; + if ((downloadCount == 0) + || (int) (totalSize * 100 / updateTotalSize) - 4 > downloadCount) { + downloadCount += 4; + Message msg = mHandler.obtainMessage(); + msg.what = 1; + msg.arg1 = downloadCount; + mHandler.sendMessage(msg); + } + } + + mHandler.sendEmptyMessage(0); + isDownload = false; + + } finally { + if (httpConnection != null) { + httpConnection.disconnect(); + } + StreamUtil.close(is, fos); + stopSelf(); + } + return totalSize; + } + + private void installApk() { + File file = new File(saveFileName + "osc.apk"); + if (!file.exists()) + return; + Intent intent = new Intent(Intent.ACTION_VIEW); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + Uri contentUri = FileProvider.getUriForFile(getApplicationContext(), "net.oschina.app.provider", file); + intent.setDataAndType(contentUri, "application/vnd.android.package-archive"); + } else { + intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + startActivity(intent); + } + + private void setUpNotification() { + int icon = R.mipmap.ic_launcher; + CharSequence tickerText = "开始下载"; + long when = System.currentTimeMillis(); + mNotification = new Notification(icon, tickerText, when); + + mNotification.flags = Notification.FLAG_ONGOING_EVENT; + + RemoteViews contentView = new RemoteViews(getPackageName(), + R.layout.layout_notification_view); + contentView.setTextViewText(R.id.tv_download_progress, mTitle); + mNotification.contentView = contentView; + + Intent intent = new Intent(this, MainActivity.class); + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + intent, PendingIntent.FLAG_UPDATE_CURRENT); + + mNotification.contentIntent = contentIntent; + mNotificationManager.notify(0, mNotification); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onDestroy() { + isDownload = false; + super.onDestroy(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/update/OSCSharedPreference.java b/app/src/main/java/net/oschina/app/improve/main/update/OSCSharedPreference.java new file mode 100644 index 0000000000000000000000000000000000000000..739cc819338ac9cd6dcbc8ffc4f4db488e9f09ac --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/update/OSCSharedPreference.java @@ -0,0 +1,193 @@ +package net.oschina.app.improve.main.update; + +import android.content.Context; +import android.text.TextUtils; + +/** + * 应用存储 + * Created by huanghaibin on 2017/6/27. + */ +@SuppressWarnings("unused") +public final class OSCSharedPreference extends SharedPreferenceUtil { + + private static OSCSharedPreference mInstance; + + public static void init(Context context, String name) { + if (mInstance == null) { + mInstance = new OSCSharedPreference(context, name); + } + } + + public static OSCSharedPreference getInstance() { + return mInstance; + } + + private OSCSharedPreference(Context context, String name) { + super(context, name); + } + + /** + * 点击更新过的版本 + */ + void putUpdateVersion(int code) { + put("osc_update_code", code); + } + + /** + * 设置更新过的版本 + */ + public int getUpdateVersion() { + return getInt("osc_update_code", 0); + } + + /** + * 设置不弹出更新 + */ + public void putShowUpdate(boolean isShow) { + put("osc_update_show", isShow); + } + + /** + * 是否弹出更新 + * 或者是新版本重新更新 259200000 + */ + boolean isShowUpdate() { + return getBoolean("osc_update_show", true); + } + + /** + * 是否已经弹出更新 + * + * @return 不弹出更新代表已经更新 + */ + public boolean hasShowUpdate() { + return !getBoolean("osc_update_show", true); + } + + /** + * 设备唯一标示 + * + * @param id id + */ + public void putDeviceUUID(String id) { + put("osc_device_uuid", id); + } + + /** + * 设备唯一标示 + */ + public String getDeviceUUID() { + return getString("osc_device_uuid", ""); + } + + + /** + * 第一次安装 + */ + public void putFirstInstall() { + put("osc_first_install", false); + } + + /** + * 第一次安装 + */ + public boolean isFirstInstall() { + return getBoolean("osc_first_install", true); + } + + /** + * 第一次使用 + */ + public void putFirstUsing() { + put("osc_first_using_v2", false); + } + + /** + * 第一次使用 + */ + public boolean isFirstUsing() { + return getBoolean("osc_first_using_v2", true); + } + + + /** + * putLastNewsId + * + * @param id id + */ + public void putLastNewsId(long id) { + put("last_news_id", id); + } + + /** + * 获取最新的id + * + * @return return + */ + public long getLastNewsId() { + return getLong("last_news_id", 0); + } + + + /** + * 返回新闻有多少 + */ + public long getTheNewsId() { + return getLong("the_last_news_id", 0); + } + + /** + * 存储最新新闻数量 + */ + public void putTheNewsId(long count) { + put("the_last_news_id", count); + } + + + /** + * 关联剪切版 + * + * @param isRelate isRelate + */ + public void putRelateClip(boolean isRelate) { + put("osc_is_relate_clip", isRelate); + } + + /** + * 是否关联剪切版 + * + * @return 是否关联剪切版 + */ + public boolean isRelateClip() { + return getBoolean("osc_is_relate_clip", true); + } + + /** + * 最后一次分享的url + * + * @param url 最后一次分享的url + */ + public void putLastShareUrl(String url) { + if (TextUtils.isEmpty(url)) + return; + put("osc_last_share_url", url); + } + + /** + * 最后一次分享的url + * + * @return 最后一次分享的url + */ + public String getLastShareUrl() { + return getString("osc_last_share_url", ""); + } + + + public boolean isFirstOpenUrl() { + return getBoolean("osc_is_first_open_url", true); + } + + public void putFirstOpenUrl() { + put("osc_is_first_open_url", false); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/update/SharedPreferenceUtil.java b/app/src/main/java/net/oschina/app/improve/main/update/SharedPreferenceUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..35aadcff5dfc65cb66778ecb204b46f5c162793c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/update/SharedPreferenceUtil.java @@ -0,0 +1,72 @@ +package net.oschina.app.improve.main.update; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; + +/** + * 配置类 + * Created by huanghaibin on 2017/6/27. + */ +@SuppressWarnings("unused") +public class SharedPreferenceUtil { + private SharedPreferences mSp; + private SharedPreferences.Editor mEditor; + + @SuppressLint("CommitPrefEdits") + SharedPreferenceUtil(Context context, String name) { + this.mSp = context.getSharedPreferences(name, Context.MODE_PRIVATE); + this.mEditor = mSp.edit(); + } + + public String getString(String key, String defaultStr) { + return mSp.getString(key, defaultStr); + } + + protected boolean getBoolean(String key, boolean defaultBoolean) { + return mSp.getBoolean(key, defaultBoolean); + } + + public int getInt(String key, int defaultInt) { + return mSp.getInt(key, defaultInt); + } + + public long getLong(String key, long defaultLong) { + return mSp.getLong(key, defaultLong); + } + + public float getFloat(String key, float defaultFloat) { + return mSp.getFloat(key, defaultFloat); + } + + public void put(String key, String value) { + if (key == null || value == null) { + return; + } + mEditor.putString(key, value); + mEditor.commit(); + } + + public void put(String key, boolean value) { + mEditor.putBoolean(key, value); + mEditor.commit(); + } + + public void put(String key, int value) { + mEditor.putInt(key, value); + mEditor.commit(); + } + + public void put(String key, double value) { + put(key, String.valueOf(value)); + } + + public void put(String key, float value) { + put(key, String.valueOf(value)); + } + + public void put(String key, long value){ + mEditor.putLong(key,value); + mEditor.commit(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/main/update/UpdateActivity.java b/app/src/main/java/net/oschina/app/improve/main/update/UpdateActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..0830a80352d8148810d1e14a1a561b49980a89e7 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/main/update/UpdateActivity.java @@ -0,0 +1,128 @@ +package net.oschina.app.improve.main.update; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.text.Html; +import android.view.View; +import android.view.WindowManager; +import android.widget.TextView; + +import net.oschina.app.BuildConfig; +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.bean.Version; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.util.TDevice; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 新版在线更新界面 + * Created by huanghaibin on 2018/4/23. + */ + +public class UpdateActivity extends BaseActivity implements View.OnClickListener { + @Bind(R.id.tv_update_info) + TextView mTextUpdateInfo; + private Version mVersion; + private static final int RC_EXTERNAL_STORAGE = 0x04;//存储权限 + + public static void show(Activity activity, Version version) { + Intent intent = new Intent(activity, UpdateActivity.class); + intent.putExtra("version", version); + activity.startActivityForResult(intent, 0x01); + } + + @Override + protected int getContentView() { + return R.layout.activity_update; + } + + @SuppressWarnings("deprecation") + @Override + protected void initData() { + super.initData(); + setTitle(""); + getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); + mVersion = (Version) getIntent().getSerializableExtra("version"); + mTextUpdateInfo.setText(Html.fromHtml(mVersion.getMessage())); + } + + @OnClick({R.id.btn_update, R.id.btn_close, R.id.btn_not_show}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_update: + if (!TDevice.isWifiOpen()) { + DialogHelper.getConfirmDialog(this, "当前非wifi环境,是否升级?", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + checkPermission(); + } + }).show(); + } else { + checkPermission(); + } + break; + case R.id.btn_not_show: + OSCSharedPreference.getInstance().putShowUpdate(false); + OSCSharedPreference.getInstance().putUpdateVersion(BuildConfig.VERSION_CODE); + finish(); + break; + case R.id.btn_close: + finish(); + break; + } + + } + + @SuppressLint("InlinedApi") + private void checkPermission() { + int result = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); + if (result != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + RC_EXTERNAL_STORAGE); + } else { + DownloadService.startService(this, mVersion.getDownloadUrl()); + finish(); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == RC_EXTERNAL_STORAGE) { + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + DownloadService.startService(this, mVersion.getDownloadUrl()); + finish(); + } else { + DialogHelper.getConfirmDialog(this, "温馨提示", + "需要开启开源中国对您手机的存储权限才能下载安装,是否现在开启", + "去开启", "取消", + true, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + finish(); + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }).show(); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/media/ImageFolderPopupWindow.java b/app/src/main/java/net/oschina/app/improve/media/ImageFolderPopupWindow.java new file mode 100644 index 0000000000000000000000000000000000000000..ddb6816ba86feeb848bc218935ecdc9198ee59db --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/ImageFolderPopupWindow.java @@ -0,0 +1,105 @@ +package net.oschina.app.improve.media; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.os.Build; +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 android.widget.PopupWindow; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.media.adapter.ImageFolderAdapter; +import net.oschina.app.improve.media.bean.ImageFolder; + +/** + * 图片选择器菜单选择界面 + */ +public class ImageFolderPopupWindow extends PopupWindow implements + View.OnAttachStateChangeListener, + BaseRecyclerAdapter.OnItemClickListener { + private ImageFolderAdapter mAdapter; + private RecyclerView mFolderView; + private Callback mCallback; + + @SuppressLint("InflateParams") + ImageFolderPopupWindow(Context context, Callback callback) { + super(LayoutInflater.from(context).inflate(R.layout.popup_window_folder, null), + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + mCallback = callback; + + // init + setAnimationStyle(R.style.popup_anim_style_alpha); + setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + setOutsideTouchable(true); + setFocusable(true); + + // content + View content = getContentView(); + content.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dismiss(); + } + }); + content.addOnAttachStateChangeListener(this); + + mFolderView = (RecyclerView) content.findViewById(R.id.rv_popup_folder); + mFolderView.setLayoutManager(new LinearLayoutManager(context)); + + } + + public void setAdapter(ImageFolderAdapter adapter) { + this.mAdapter = adapter; + mFolderView.setAdapter(adapter); + mAdapter.setOnItemClickListener(this); + } + + @Override + public void showAsDropDown(View anchor) { + if(Build.VERSION.SDK_INT >= 24){ + Rect visibleFrame = new Rect(); + anchor.getGlobalVisibleRect(visibleFrame); + int height = anchor.getResources().getDisplayMetrics().heightPixels - visibleFrame.bottom; + setHeight(height); + } + + super.showAsDropDown(anchor); + } + + @Override + public void onViewAttachedToWindow(View v) { + final Callback callback = mCallback; + if (callback != null) + callback.onShow(); + } + + @Override + public void onViewDetachedFromWindow(View v) { + final Callback callback = mCallback; + if (callback != null) + callback.onDismiss(); + } + + @Override + public void onItemClick(int position, long itemId) { + final Callback callback = mCallback; + if (callback != null) + callback.onSelect(this, mAdapter.getItem(position)); + } + + public interface Callback { + void onSelect(ImageFolderPopupWindow popupWindow, ImageFolder model); + + void onDismiss(); + + void onShow(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/ImageGalleryActivity.java b/app/src/main/java/net/oschina/app/improve/media/ImageGalleryActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..af1020f494f51dce186b96268253f067d37aa287 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/ImageGalleryActivity.java @@ -0,0 +1,503 @@ +package net.oschina.app.improve.media; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.graphics.Point; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.bumptech.glide.DrawableRequestBuilder; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; + +import net.oschina.app.R; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.common.utils.BitmapUtil; +import net.oschina.common.utils.StreamUtil; +import net.oschina.common.widget.Loading; + +import java.io.File; +import java.util.List; +import java.util.concurrent.Future; + +import butterknife.OnClick; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.AppSettingsDialog; +import pub.devrel.easypermissions.EasyPermissions; + + +/** + * 图片预览Activity + */ +public class ImageGalleryActivity extends BaseActivity implements ViewPager.OnPageChangeListener, + EasyPermissions.PermissionCallbacks { + public static final String KEY_IMAGE = "images"; + public static final String KEY_COOKIE = "cookie_need"; + public static final String KEY_POSITION = "position"; + public static final String KEY_NEED_SAVE = "save"; + private PreviewerViewPager mImagePager; + private TextView mIndexText; + private String[] mImageSources; + private int mCurPosition; + private boolean mNeedSaveLocal; + private boolean mNeedCookie; + private boolean[] mImageDownloadStatus; + + public static void show(Context context, String images) { + show(context, images, true); + } + + public static void show(Context context, String images, boolean needSaveLocal) { + if (images == null) + return; + show(context, new String[]{images}, 0, needSaveLocal); + } + + public static void show(Context context, String images, boolean needSaveLocal, boolean needCookie) { + if (images == null) + return; + show(context, new String[]{images}, 0, needSaveLocal, needCookie); + } + + public static void show(Context context, String[] images, int position) { + show(context, images, position, true); + } + + public static void show(Context context, String[] images, int position, boolean needSaveLocal) { + show(context, images, position, needSaveLocal, false); + } + + public static void show(Context context, String[] images, int position, boolean needSaveLocal, boolean needCookie) { + if (images == null || images.length == 0) + return; + if (images.length == 1 && !images[0].endsWith(".gif") && !images[0].endsWith(".GIF") && !needCookie) { + LargeImageActivity.show(context, images[0]); + return; + } + Intent intent = new Intent(context, ImageGalleryActivity.class); + intent.putExtra(KEY_IMAGE, images); + intent.putExtra(KEY_POSITION, position); + intent.putExtra(KEY_NEED_SAVE, needSaveLocal); + intent.putExtra(KEY_COOKIE, needCookie); + context.startActivity(intent); + } + + @Override + protected boolean initBundle(Bundle bundle) { + mImageSources = bundle.getStringArray(KEY_IMAGE); + mCurPosition = bundle.getInt(KEY_POSITION, 0); + mNeedSaveLocal = bundle.getBoolean(KEY_NEED_SAVE, true); + mNeedCookie = bundle.getBoolean(KEY_COOKIE, false); + + if (mImageSources != null) { + // 初始化下载状态 + mImageDownloadStatus = new boolean[mImageSources.length]; + return true; + } + + return false; + } + + @Override + protected void initWindow() { + super.initWindow(); + setSwipeBackEnable(false); + getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); + } + + @Override + protected int getContentView() { + return R.layout.activity_image_gallery; + } + + @Override + protected void initWidget() { + super.initWidget(); + setTitle(""); + + mImagePager = (PreviewerViewPager) findViewById(R.id.vp_image); + mIndexText = (TextView) findViewById(R.id.tv_index); + mImagePager.addOnPageChangeListener(this); + } + + @Override + protected void initData() { + super.initData(); + int len = mImageSources.length; + if (mCurPosition < 0 || mCurPosition >= len) + mCurPosition = 0; + + // If only one, we not need the text to show + if (len == 1) + mIndexText.setVisibility(View.GONE); + + mImagePager.setAdapter(new ViewPagerAdapter()); + mImagePager.setCurrentItem(mCurPosition); + // First we call to init the TextView + onPageSelected(mCurPosition); + } + + private void changeSaveButtonStatus(boolean isShow) { + if (mNeedSaveLocal) { + findViewById(R.id.iv_save).setVisibility(isShow ? View.VISIBLE : View.GONE); + } else + findViewById(R.id.iv_save).setVisibility(View.GONE); + } + + private void updateDownloadStatus(int pos, boolean isOk) { + mImageDownloadStatus[pos] = isOk; + if (mCurPosition == pos) { + changeSaveButtonStatus(isOk); + } + } + + private static final int PERMISSION_ID = 0x0001; + + @SuppressWarnings("unused") + @AfterPermissionGranted(PERMISSION_ID) + @OnClick(R.id.iv_save) + public void saveToFileByPermission() { + String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + if (EasyPermissions.hasPermissions(this, permissions)) { + saveToFile(); + } else { + EasyPermissions.requestPermissions(this, "请授予保存图片权限", PERMISSION_ID, permissions); + } + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + Toast.makeText(this, R.string.gallery_save_file_not_have_external_storage_permission, Toast.LENGTH_SHORT).show(); + if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { + new AppSettingsDialog.Builder(this).build().show(); + } + } + + private void saveToFile() { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + Toast.makeText(this, R.string.gallery_save_file_not_have_external_storage, Toast.LENGTH_SHORT).show(); + return; + } + + String path = mImageSources[mCurPosition]; + + Object urlOrPath; + // Do load + if (mNeedCookie) + urlOrPath = AppOperator.getGlideUrlByUser(path); + else + urlOrPath = path; + + // In this save max image size is source + final Future future = getImageLoader() + .load(urlOrPath) + .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL); + + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + try { + File sourceFile = future.get(); + if (sourceFile == null || !sourceFile.exists()) + return; + String extension = BitmapUtil.getExtension(sourceFile.getAbsolutePath()); + String extDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + .getAbsolutePath() + File.separator + "开源中国"; + File extDirFile = new File(extDir); + if (!extDirFile.exists()) { + if (!extDirFile.mkdirs()) { + // If mk dir error + callSaveStatus(false, null); + return; + } + } + final File saveFile = new File(extDirFile, String.format("IMG_%s.%s", System.currentTimeMillis(), extension)); + final boolean isSuccess = StreamUtil.copyFile(sourceFile, saveFile); + callSaveStatus(isSuccess, saveFile); + } catch (Exception e) { + e.printStackTrace(); + callSaveStatus(false, null); + } + } + }); + } + + private void callSaveStatus(final boolean success, final File savePath) { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (success) { + // notify + Uri uri = Uri.fromFile(savePath); + sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)); + Toast.makeText(ImageGalleryActivity.this, R.string.gallery_save_file_success, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(ImageGalleryActivity.this, R.string.gallery_save_file_failed, Toast.LENGTH_SHORT).show(); + } + } + }); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + mCurPosition = position; + mIndexText.setText(String.format("%s/%s", (position + 1), mImageSources.length)); + // 滑动时自动切换当前的下载状态 + changeSaveButtonStatus(mImageDownloadStatus[position]); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + + private Point mDisplayDimens; + + @SuppressLint("ObsoleteSdkInt") + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) + @SuppressWarnings("deprecation") + private synchronized Point getDisplayDimens() { + if (mDisplayDimens != null) { + return mDisplayDimens; + } + Point displayDimens; + WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + assert windowManager != null; + Display display = windowManager.getDefaultDisplay(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { + displayDimens = new Point(); + display.getSize(displayDimens); + } else { + displayDimens = new Point(display.getWidth(), display.getHeight()); + } + + // In this we can only get 85% width and 60% height + //displayDimens.y = (int) (displayDimens.y * 0.60f); + //displayDimens.x = (int) (displayDimens.x * 0.85f); + + mDisplayDimens = displayDimens; + return mDisplayDimens; + } + + private class ViewPagerAdapter extends PagerAdapter implements ImagePreviewView.OnReachBorderListener { + + private View.OnClickListener mFinishClickListener; + + @Override + public int getCount() { + return mImageSources.length; + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + View view = LayoutInflater.from(container.getContext()) + .inflate(R.layout.lay_gallery_page_item_contener, container, false); + ImagePreviewView previewView = (ImagePreviewView) view.findViewById(R.id.iv_preview); + previewView.setOnReachBorderListener(this); + Loading loading = (Loading) view.findViewById(R.id.loading); + ImageView defaultView = (ImageView) view.findViewById(R.id.iv_default); + + // Do load + if (mNeedCookie) + loadImage(position, AppOperator.getGlideUrlByUser(mImageSources[position]), + previewView, defaultView, loading); + else + loadImage(position, mImageSources[position], previewView, defaultView, loading); + + previewView.setOnClickListener(getListener()); + container.addView(view); + return view; + } + + private View.OnClickListener getListener() { + if (mFinishClickListener == null) { + mFinishClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }; + } + return mFinishClickListener; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + container.removeView((View) object); + } + + @Override + public void onReachBorder(boolean isReached) { + mImagePager.isInterceptable(isReached); + } + + private void loadImage(final int pos, final T urlOrPath, + final ImageView previewView, + final ImageView defaultView, + final Loading loading) { + + loadImageDoDownAndGetOverrideSize(urlOrPath, new DoOverrideSizeCallback() { + @Override + public void onDone(int overrideW, int overrideH, boolean isTrue) { + DrawableRequestBuilder builder = getImageLoader() + .load(urlOrPath) + .listener(new RequestListener() { + @Override + public boolean onException(Exception e, + T model, + Target target, + boolean isFirstResource) { + if (e != null) + e.printStackTrace(); + loading.stop(); + loading.setVisibility(View.GONE); + defaultView.setVisibility(View.VISIBLE); + updateDownloadStatus(pos, false); + return false; + } + + @Override + public boolean onResourceReady(GlideDrawable resource, + T model, + Target target, + boolean isFromMemoryCache, + boolean isFirstResource) { + loading.stop(); + loading.setVisibility(View.GONE); + //previewView.setImageDrawable(resource); + updateDownloadStatus(pos, true); + return false; + } + }).diskCacheStrategy(DiskCacheStrategy.SOURCE); + + // If download or get option error we not set override + if (isTrue && overrideW > 0 && overrideH > 0) { + builder = builder.override(overrideW, overrideH).fitCenter(); + } + + builder.into(previewView); + } + }); + } + + private void loadImageDoDownAndGetOverrideSize(final T urlOrPath, final DoOverrideSizeCallback callback) { + // In this save max image size is source + final Future future = getImageLoader().load(urlOrPath) + .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL); + + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + try { + File sourceFile = future.get(); + + BitmapFactory.Options options = BitmapUtil.createOptions(); + // First decode with inJustDecodeBounds=true to checkShare dimensions + options.inJustDecodeBounds = true; + // First decode with inJustDecodeBounds=true to checkShare dimensions + BitmapFactory.decodeFile(sourceFile.getAbsolutePath(), options); + + int width = options.outWidth; + int height = options.outHeight; + BitmapUtil.resetOptions(options); + + if (width > 0 && height > 0) { + // Get Screen + final Point point = getDisplayDimens(); + + // This max size + final int maxLen = Math.min(Math.min(point.y, point.x) * 5, 1366 * 3); + + // Init override size + final int overrideW, overrideH; + + if ((width / (float) height) > (point.x / (float) point.y)) { + overrideH = Math.min(height, point.y); + overrideW = Math.min(width, maxLen); + } else { + overrideW = Math.min(width, point.x); + overrideH = Math.min(height, maxLen); + } + + // Call back on main thread + runOnUiThread(new Runnable() { + @Override + public void run() { + callback.onDone(overrideW, overrideH, true); + } + }); + } else { + // Call back on main thread + runOnUiThread(new Runnable() { + @Override + public void run() { + callback.onDone(0, 0, false); + } + }); + } + } catch (Exception e) { + e.printStackTrace(); + + // Call back on main thread + runOnUiThread(new Runnable() { + @Override + public void run() { + callback.onDone(0, 0, false); + } + }); + } + } + }); + } + + + } + + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + // Forward results to EasyPermissions + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + interface DoOverrideSizeCallback { + void onDone(int overrideW, int overrideH, boolean isTrue); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/ImagePreviewView.java b/app/src/main/java/net/oschina/app/improve/media/ImagePreviewView.java new file mode 100644 index 0000000000000000000000000000000000000000..31a8aafe4c7f5137364f4b83e56a9eb93763b9b9 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/ImagePreviewView.java @@ -0,0 +1,542 @@ +package net.oschina.app.improve.media; + +import android.animation.FloatEvaluator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; + +/** + * 支持图片预览, 放大,缩小,位置自适应,双击放大缩小 + * Created by thanatosx on 16/5/3. + */ +public class ImagePreviewView extends ImageView { + + private ScaleGestureDetector mScaleDetector; + private GestureDetector mFlatDetector; + + private float scale = 1.f; + private static final float mMaxScale = 4.f; + private static final float mMinScale = 0.4f; + private float translateLeft = 0.f; + private float translateTop = 0.f; + private int mBoundWidth = 0; + private int mBoundHeight = 0; + private boolean isAutoScale = false; + + private ValueAnimator resetScaleAnimator; + private ValueAnimator resetXAnimator; + private ValueAnimator resetYAnimator; + private ValueAnimator.AnimatorUpdateListener onScaleAnimationUpdate; + private ValueAnimator.AnimatorUpdateListener onTranslateXAnimationUpdate; + private ValueAnimator.AnimatorUpdateListener onTranslateYAnimationUpdate; + + private FloatEvaluator mFloatEvaluator = new FloatEvaluator(); + private AccelerateInterpolator mAccInterpolator = new AccelerateInterpolator(); + private DecelerateInterpolator mDecInterpolator = new DecelerateInterpolator(); + + private OnReachBorderListener onReachBorderListener; + + public interface OnReachBorderListener { + void onReachBorder(boolean isReached); + } + + public void setOnReachBorderListener(OnReachBorderListener l) { + onReachBorderListener = l; + } + + public ImagePreviewView(Context context) { + this(context, null); + } + + public ImagePreviewView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ImagePreviewView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener()); + mFlatDetector = new GestureDetector(getContext(), new FlatGestureListener()); + } + + /** + * 重置伸缩动画的监听器 + * + * @return + */ + public ValueAnimator.AnimatorUpdateListener getOnScaleAnimationUpdate() { + if (onScaleAnimationUpdate != null) return onScaleAnimationUpdate; + onScaleAnimationUpdate = new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + scale = (float) animation.getAnimatedValue(); + invalidate(); + } + }; + return onScaleAnimationUpdate; + } + + /** + * 重置水平动画的监听器 + * + * @return + */ + public ValueAnimator.AnimatorUpdateListener getOnTranslateXAnimationUpdate() { + if (onTranslateXAnimationUpdate != null) return onTranslateXAnimationUpdate; + onTranslateXAnimationUpdate = new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + translateLeft = (float) animation.getAnimatedValue(); + invalidate(); + } + }; + return onTranslateXAnimationUpdate; + } + + /** + * 重置垂直动画的监听器 + * + * @return + */ + public ValueAnimator.AnimatorUpdateListener getOnTranslateYAnimationUpdate() { + if (onTranslateYAnimationUpdate != null) return onTranslateYAnimationUpdate; + onTranslateYAnimationUpdate = new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + translateTop = (float) animation.getAnimatedValue(); + invalidate(); + } + }; + return onTranslateYAnimationUpdate; + } + + /** + * 重置伸缩 + * + * @return the animator control scale value + */ + private ValueAnimator getResetScaleAnimator() { + if (resetScaleAnimator != null) { + resetScaleAnimator.removeAllUpdateListeners(); + } else { + resetScaleAnimator = ValueAnimator.ofFloat(); + } + resetScaleAnimator.setDuration(150); + resetScaleAnimator.setInterpolator(mAccInterpolator); + resetScaleAnimator.setEvaluator(mFloatEvaluator); + return resetScaleAnimator; + } + + /** + * 水平方向的重置动画 + * + * @return + */ + private ValueAnimator getResetXAnimator() { + if (resetXAnimator != null) { + resetXAnimator.removeAllUpdateListeners(); + } else { + resetXAnimator = ValueAnimator.ofFloat(); + } + resetXAnimator.setDuration(150); + resetXAnimator.setInterpolator(mAccInterpolator); + resetXAnimator.setEvaluator(mFloatEvaluator); + return resetXAnimator; + } + + /** + * 垂直方向的重置动画 + * + * @return + */ + private ValueAnimator getResetYAnimator() { + if (resetYAnimator != null) { + resetYAnimator.removeAllUpdateListeners(); + } else { + resetYAnimator = ValueAnimator.ofFloat(); + } + resetYAnimator.setDuration(150); + resetYAnimator.setInterpolator(mAccInterpolator); + resetYAnimator.setEvaluator(mFloatEvaluator); + return resetYAnimator; + } + + private void cancelAnimation() { + if (resetScaleAnimator != null && resetScaleAnimator.isRunning()) { + resetScaleAnimator.cancel(); + } + if (resetXAnimator != null && resetXAnimator.isRunning()) { + resetXAnimator.cancel(); + } + if (resetYAnimator != null && resetYAnimator.isRunning()) { + resetYAnimator.cancel(); + } + } + + /** + * @return 如果是正数, 左边有空隙, 如果是负数, 右边有空隙, 如果是0, 代表两边都没有空隙 + */ + private float getDiffX() { + final float mScaledWidth = mBoundWidth * scale; + return translateLeft >= 0 + ? translateLeft + : getWidth() - translateLeft - mScaledWidth > 0 + ? -(getWidth() - translateLeft - mScaledWidth) + : 0; + } + + /** + * @return 如果是正数, 上面有空隙, 如果是负数, 下面有空隙, 如果是0, 代表两边都没有空隙 + */ + private float getDiffY() { + final float mScaledHeight = mBoundHeight * scale; + return translateTop >= 0 + ? translateTop + : getHeight() - translateTop - mScaledHeight > 0 + ? -(getHeight() - translateTop - mScaledHeight) + : 0; + } + + + @Override + public boolean onTouchEvent(MotionEvent event) { + final int action = event.getAction(); + + // 清理动画 + if (action == MotionEvent.ACTION_DOWN) { + cancelAnimation(); + } + + mFlatDetector.onTouchEvent(event); + mScaleDetector.onTouchEvent(event); + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + if (isAutoScale) { + isAutoScale = false; + } else { + if (scale < 1) { + ValueAnimator animator = getResetScaleAnimator(); + animator.setFloatValues(scale, 1.f); + animator.addUpdateListener(getOnScaleAnimationUpdate()); + animator.start(); + } + final float mScaledWidth = mBoundWidth * scale; + final float mScaledHeight = mBoundHeight * scale; + + final float mDiffX = getDiffX(); + final float mDiffY = getDiffY(); + + // 左右边界重置 + if (mScaledWidth >= getWidth() && mDiffX != 0) { + ValueAnimator animator = getResetXAnimator(); + animator.setFloatValues(translateLeft, translateLeft - mDiffX); + animator.addUpdateListener(getOnTranslateXAnimationUpdate()); + animator.start(); + } + + // 上下边界重置 + if (mScaledHeight >= getHeight() && mDiffY != 0) { + ValueAnimator animator = getResetYAnimator(); + animator.setFloatValues(translateTop, translateTop - mDiffY); + animator.addUpdateListener(getOnTranslateYAnimationUpdate()); + animator.start(); + } + + // width重置到中间位置 + if (mScaledWidth < getWidth() && mScaledHeight >= getHeight() && mDiffX != 0) { + ValueAnimator animator = getResetXAnimator(); + animator.setFloatValues(translateLeft, 0); // 宽度总是填充的 + animator.addUpdateListener(getOnTranslateXAnimationUpdate()); + animator.start(); + } + + // height重置到中间位置 + if (mScaledHeight < getHeight() && mScaledWidth >= getWidth() && mDiffY != 0) { + ValueAnimator animator = getResetYAnimator(); + animator.setFloatValues(translateTop, (getHeight() - mScaledHeight) / 2.f); + animator.addUpdateListener(getOnTranslateYAnimationUpdate()); + animator.start(); + } + + if (mScaledWidth < getWidth() && mScaledHeight < getHeight()) { + resetDefaultState(); + } + } + } + + return true; + } + + private void resetDefaultState() { + if (translateLeft != 0) { + ValueAnimator mTranslateXAnimator = getResetXAnimator(); + mTranslateXAnimator.setFloatValues(translateLeft, 0); + mTranslateXAnimator.addUpdateListener(getOnTranslateXAnimationUpdate()); + mTranslateXAnimator.start(); + } + + ValueAnimator mTranslateYAnimator = getResetYAnimator(); + mTranslateYAnimator.setFloatValues(translateTop, getDefaultTranslateTop(getHeight(), mBoundHeight)); + mTranslateYAnimator.addUpdateListener(getOnTranslateYAnimationUpdate()); + mTranslateYAnimator.start(); + + } + + @Override + protected boolean setFrame(int l, int t, int r, int b) { + super.setFrame(l, t, r, b); + + Drawable drawable = getDrawable(); + if (drawable == null) return false; + if (mBoundWidth != 0 && mBoundHeight != 0 && scale != 1) return false; + + adjustBounds(getWidth(), getHeight()); + + return true; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + adjustBounds(w, h); + } + + private void adjustBounds(int width, int height) { + Drawable drawable = getDrawable(); + if (drawable == null) return; + mBoundWidth = drawable.getBounds().width(); + mBoundHeight = drawable.getBounds().height(); + + float scale = (float) mBoundWidth / width; + + mBoundHeight /= scale; + mBoundWidth = width; + + drawable.setBounds(0, 0, mBoundWidth, mBoundHeight); + + translateLeft = 0; + translateTop = getDefaultTranslateTop(height, mBoundHeight); + } + + private float getDefaultTranslateTop(int height, int bh) { + float top = (height - bh) / 2.f; + return top > 0 ? top : 0; + } + + @Override + protected void onDraw(Canvas canvas) { + Drawable mDrawable = getDrawable(); + if (mDrawable == null) return; + + final int mDrawableWidth = mDrawable.getIntrinsicWidth(); + final int mDrawableHeight = mDrawable.getIntrinsicHeight(); + + if (mDrawableWidth == 0 || mDrawableHeight == 0) { + return; // nothing to draw (empty bounds) + } + + int saveCount = canvas.getSaveCount(); + canvas.save(); + + canvas.translate(translateLeft, translateTop); + canvas.scale(scale, scale); + + // 如果先scale,再translate,那么,真实translate的值是要与scale值相乘的 + mDrawable.draw(canvas); + canvas.restoreToCount(saveCount); + } + + private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + /** + * factor = detector.getCurrentSpan() / detector.getPreviousSpan() + * + * @param detector + * @return + */ + @Override + public boolean onScale(ScaleGestureDetector detector) { + final float mOldScaledWidth = mBoundWidth * scale; + final float mOldScaledHeight = mBoundHeight * scale; + + if (mOldScaledWidth > getWidth() && getDiffX() != 0 || + (mOldScaledHeight > getHeight() && getDiffY() != 0)) return false; + + float factor = detector.getScaleFactor(); + float value = scale; + value += (factor - 1) * 2; + if (value == scale) return true; + if (value <= mMinScale) return false; + if (value > mMaxScale) return false; + scale = value; + final float mScaledWidth = mBoundWidth * scale; + final float mScaledHeight = mBoundHeight * scale; + + // 走了些弯路, 不应该带入translateX计算, 因为二次放大之后计算就不正确了,它应该受scale的制约 + translateLeft = getWidth() / 2.f - (getWidth() / 2.f - translateLeft) * mScaledWidth / mOldScaledWidth; + translateTop = getHeight() / 2.f - (getHeight() / 2.f - translateTop) * mScaledHeight / mOldScaledHeight; + + final float diffX = getDiffX(); + final float diffY = getDiffY(); + + // 考虑宽图, 如果缩小的时候图片左边界到了屏幕左边界,停留在左边界缩小 + if (diffX > 0 && mScaledWidth > getWidth()) { + translateLeft = 0; + } + // 右边界问题 + if (diffX < 0 && mScaledWidth > getWidth()) { + translateLeft = getWidth() - mScaledWidth; + } + + // 考虑到长图,上边界问题 + if (diffY > 0 && mScaledHeight > getHeight()) { + translateTop = 0; + } + + // 下边界问题 + if (diffY < 0 && mScaledHeight > getHeight()) { + translateTop = getHeight() - mScaledHeight; + } + + invalidate(); + return true; + } + + } + + private float getExplicitTranslateLeft(float l) { + final float mScaledWidth = mBoundWidth * scale; + if (l > 0) { + l = 0; + } + if (-l + getWidth() > mScaledWidth) { + l = getWidth() - mScaledWidth; + } + return l; + } + + private float getExplicitTranslateTop(float t) { + final float mScaledHeight = mBoundHeight * scale; + if (t > 0) { + t = 0; + } + if (-t + getHeight() > mScaledHeight) { + t = getHeight() - mScaledHeight; + } + return t; + } + + private class FlatGestureListener extends GestureDetector.SimpleOnGestureListener { + /** + * @param e1 horizontal event + * @param e2 vertical event + * @param distanceX previous X - current X, toward left , is position + * @param distanceY previous Y - current Y, toward up, is position + * @return + */ + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + final float mScaledWidth = mBoundWidth * scale; + final float mScaledHeight = mBoundHeight * scale; + + if (mScaledHeight > getHeight()) { + translateTop -= distanceY * 1.5; + translateTop = getExplicitTranslateTop(translateTop); + /*if (getDiffY() != 0) { + final float disY = (float) (Math.acos(Math.abs(getDiffY()) / getPanelHeight() * 6) * distanceY); + if (disY == disY) translateTop -= disY; // float 低值溢出变Nan数值 + } else { + translateTop -= distanceY * 1.5; + }*/ + } + + boolean isReachBorder = false; + if (mScaledWidth > getWidth()) { + translateLeft -= distanceX * 1.5; + final float t = getExplicitTranslateLeft(translateLeft); + if (t != translateLeft) isReachBorder = true; + translateLeft = t; + /*if (getDiffX() != 0) { + final float disX = (float) (Math.acos(Math.abs(getDiffX()) / getWidth() * 4) * distanceX); + if (disX == disX) translateLeft -= disX; + } else { + translateLeft -= distanceX * 1.5; + }*/ + } else { + isReachBorder = true; + } + + if (onReachBorderListener != null) + onReachBorderListener.onReachBorder(isReachBorder); + + invalidate(); + return true; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + return performClick(); + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (mBoundWidth * scale > getWidth()) { + float sx = translateLeft + (1f / 2f) * velocityX * 0.5f * 0.5f; + sx = getExplicitTranslateLeft(sx); + ValueAnimator mResetXAnimator = getResetXAnimator(); + mResetXAnimator.setDuration(300); + mResetXAnimator.setInterpolator(mDecInterpolator); + mResetXAnimator.setFloatValues(translateLeft, sx); + mResetXAnimator.addUpdateListener(getOnTranslateXAnimationUpdate()); + mResetXAnimator.start(); + } + + if (mBoundHeight * scale > getHeight()) { + float sy = translateTop + (1f / 2f) * velocityY * 0.5f * 0.5f; + sy = getExplicitTranslateTop(sy); + ValueAnimator mResetYAnimator = getResetYAnimator(); + mResetYAnimator.setDuration(300); + mResetYAnimator.setInterpolator(mDecInterpolator); + mResetYAnimator.setFloatValues(translateTop, sy); + mResetYAnimator.addUpdateListener(getOnTranslateYAnimationUpdate()); + mResetYAnimator.start(); + } + + return true; + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + isAutoScale = true; + ValueAnimator mResetScaleAnimator = getResetScaleAnimator(); + + if (scale == 1.f) { + mResetScaleAnimator.setFloatValues(1.f, 2.f); + + ValueAnimator mResetXAnimator = getResetXAnimator(); + ValueAnimator mResetYAnimator = getResetYAnimator(); + mResetXAnimator.setFloatValues(translateLeft, (getWidth() - mBoundWidth * 2.f) / 2.f); + mResetYAnimator.setFloatValues(translateTop, getDefaultTranslateTop(getHeight(), mBoundHeight * 2)); + mResetXAnimator.addUpdateListener(getOnTranslateXAnimationUpdate()); + mResetYAnimator.addUpdateListener(getOnTranslateYAnimationUpdate()); + mResetXAnimator.start(); + mResetYAnimator.start(); + } else { + mResetScaleAnimator.setFloatValues(scale, 1.f); + resetDefaultState(); + } + + mResetScaleAnimator.addUpdateListener(getOnScaleAnimationUpdate()); + mResetScaleAnimator.start(); + return true; + } + } + + +} diff --git a/app/src/main/java/net/oschina/app/improve/media/LargeImageActivity.java b/app/src/main/java/net/oschina/app/improve/media/LargeImageActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..345d17a7e7d567fede49b89f2ee350b4f4aa04db --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/LargeImageActivity.java @@ -0,0 +1,206 @@ +package net.oschina.app.improve.media; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.graphics.PointF; +import android.net.Uri; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.Toast; + +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.target.Target; +import com.davemorrissey.labs.subscaleview.ImageSource; +import com.davemorrissey.labs.subscaleview.ImageViewState; +import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; + +import net.oschina.app.R; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.common.utils.BitmapUtil; +import net.oschina.common.utils.StreamUtil; +import net.oschina.common.widget.Loading; + +import java.io.File; +import java.util.List; +import java.util.concurrent.Future; + +import butterknife.Bind; +import butterknife.OnClick; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.AppSettingsDialog; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * 大图预览 + * Created by huanghaibin on 2017/9/27. + */ + +public class LargeImageActivity extends BaseActivity implements EasyPermissions.PermissionCallbacks { + + @Bind(R.id.imageView) + SubsamplingScaleImageView mImageView; + + @SuppressWarnings("unused") + @Bind(R.id.iv_save) + ImageView mImageSave; + + @Bind(R.id.loading) + Loading mLoading; + + private String mPath; + + public static void show(Context context, String image) { + Intent intent = new Intent(context, LargeImageActivity.class); + intent.putExtra("image", image); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_large_image; + } + + @Override + protected void initWidget() { + super.initWidget(); + getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); + mImageView.setMaxScale(15); + mImageView.setZoomEnabled(true); + mImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM); + mImageView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + } + + @Override + protected void initData() { + super.initData(); + setSwipeBackEnable(false); + mPath = getIntent().getStringExtra("image"); + getImageLoader() + .load(mPath) + .downloadOnly(new SimpleTarget() { + @Override + public void onResourceReady(File resource, GlideAnimation glideAnimation) { + if (isDestroyed()) + return; + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(resource.getPath(), options); + int w = options.outWidth; + int sw = Util.getScreenWidth(LargeImageActivity.this); + float scale = (float) sw / (float) w; + mImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM); + mImageView.setImage(ImageSource.uri(Uri.fromFile(resource)), new ImageViewState(scale, + new PointF(0, 0), net.oschina.app.improve.utils.BitmapUtil.readPictureDegree(mPath))); + mImageSave.setVisibility(View.VISIBLE); + mLoading.stop(); + mLoading.setVisibility(View.GONE); + } + }); + } + + private static final int PERMISSION_ID = 0x0001; + + @SuppressWarnings("unused") + @AfterPermissionGranted(PERMISSION_ID) + @OnClick(R.id.iv_save) + public void saveToFileByPermission() { + String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + if (EasyPermissions.hasPermissions(this, permissions)) { + saveToFile(); + } else { + EasyPermissions.requestPermissions(this, "请授予保存图片权限", PERMISSION_ID, permissions); + } + } + + private void saveToFile() { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + Toast.makeText(this, R.string.gallery_save_file_not_have_external_storage, Toast.LENGTH_SHORT).show(); + return; + } + + final Future future = getImageLoader() + .load(mPath) + .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL); + + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + try { + File sourceFile = future.get(); + if (sourceFile == null || !sourceFile.exists()) + return; + String extension = BitmapUtil.getExtension(sourceFile.getAbsolutePath()); + String extDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + .getAbsolutePath() + File.separator + "开源中国"; + File extDirFile = new File(extDir); + if (!extDirFile.exists()) { + if (!extDirFile.mkdirs()) { + // If mk dir error + callSaveStatus(false, null); + return; + } + } + final File saveFile = new File(extDirFile, String.format("IMG_%s.%s", System.currentTimeMillis(), extension)); + final boolean isSuccess = StreamUtil.copyFile(sourceFile, saveFile); + callSaveStatus(isSuccess, saveFile); + } catch (Exception e) { + e.printStackTrace(); + callSaveStatus(false, null); + } + } + }); + } + + private void callSaveStatus(final boolean success, final File savePath) { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (success) { + // notify + if (isDestroyed()) + return; + Uri uri = Uri.fromFile(savePath); + sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)); + Toast.makeText(LargeImageActivity.this, R.string.gallery_save_file_success, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(LargeImageActivity.this, R.string.gallery_save_file_failed, Toast.LENGTH_SHORT).show(); + } + } + }); + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + Toast.makeText(this, R.string.gallery_save_file_not_have_external_storage_permission, Toast.LENGTH_SHORT).show(); + if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { + new AppSettingsDialog.Builder(this).build().show(); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + // Forward results to EasyPermissions + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + +} diff --git a/app/src/main/java/net/oschina/app/improve/media/MediaStoreDataFactory.java b/app/src/main/java/net/oschina/app/improve/media/MediaStoreDataFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..8e8bf504770da742c07cb6cf3b27da57f02472d4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/MediaStoreDataFactory.java @@ -0,0 +1,246 @@ +package net.oschina.app.improve.media; + +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +import android.provider.MediaStore; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.util.ArrayMap; + +import net.oschina.app.improve.media.bean.Image; +import net.oschina.app.improve.media.bean.ImageFolder; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Created by JuQiu + * on 16/7/28. + */ +public class MediaStoreDataFactory implements LoaderManager.LoaderCallbacks { + private static final String[] IMAGE_PROJECTION = { + MediaStore.Images.Media.DATA, + MediaStore.Images.Media.DISPLAY_NAME, + MediaStore.Images.Media.DATE_ADDED, + MediaStore.Images.Media._ID, + MediaStore.Images.Media.MINI_THUMB_MAGIC, + MediaStore.Images.Media.BUCKET_DISPLAY_NAME}; + + private final List mSource = new ArrayList<>(); + private final Map mFolderSource = new ArrayMap<>(); + private String mRegisterKey = ""; + private PictureSourceCallback mImageCallback; + private FolderSourceCallback mFolderCallback; + private Context mContext; + + public MediaStoreDataFactory(Context context, PictureSourceCallback pictureSourceCallback, + FolderSourceCallback folderSourceCallback) { + this.mContext = context; + this.mImageCallback = pictureSourceCallback; + this.mFolderCallback = folderSourceCallback; + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new CursorLoader(mContext, + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION, + null, null, IMAGE_PROJECTION[2] + " DESC"); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + if (data != null) { + ArrayList images = new ArrayList<>(); + int count = data.getCount(); + if (count > 0) { + data.moveToFirst(); + do { + String path = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[0])); + String name = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[1])); + long dateTime = data.getLong(data.getColumnIndexOrThrow(IMAGE_PROJECTION[2])); + int id = data.getInt(data.getColumnIndexOrThrow(IMAGE_PROJECTION[3])); + String thumbPath = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[4])); + String bucket = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[5])); + + Image image = new Image(); + image.setPath(path); + image.setName(name); + image.setDate(dateTime); + image.setId(id); + image.setThumbPath(thumbPath); + image.setFolderName(bucket); + images.add(image); + } while (data.moveToNext()); + } + doSource(images); + } + } + + + private void doSource(List changes) { + final List source = mSource; + if (source.size() == 0) { + source.addAll(changes); + callDataAdded(source); + } + if (changes.size() == 0) { + callDataRemoved(source); + source.clear(); + } else { + // checkShare remove + List removes = new ArrayList<>(); + for (Image image : source) { + if (!changes.contains(image)) + removes.add(image); + } + if (removes.size() > 0) { + source.removeAll(removes); + callDataRemoved(removes); + } + + // checkShare add + List adds = new ArrayList<>(); + for (Image image : changes) { + if (!source.contains(image)) + adds.add(image); + } + if (adds.size() > 0) { + source.addAll(adds); + callDataAdded(adds); + } + } + } + + private void callDataAdded(List images) { + List updateFolder = new ArrayList<>(); + List addFolder = new ArrayList<>(); + List addImages = new ArrayList<>(); + + final Map folderSource = mFolderSource; + for (Image image : images) { + File folderFile = new File(image.getPath()).getParentFile(); + String folderPath = folderFile.getAbsolutePath().toLowerCase(); + if (folderSource.containsKey(folderPath)) { + // update + ImageFolder folder = folderSource.get(folderPath); + folder.getImages().add(image); + if (!addFolder.contains(folder) && !updateFolder.contains(folder)) { + updateFolder.add(folder); + } + } else { + // Add new + ImageFolder folder = new ImageFolder(); + folder.setName(folderFile.getName()); + folder.setPath(folderFile.getAbsolutePath()); + folder.setAlbumPath(image.getPath()); + folder.getImages().add(image); + folderSource.put(folderPath, folder); + addFolder.add(folder); + } + + if (mRegisterKey.equals(folderPath)) { + addImages.add(image); + } + } + + notifyFolderAdd(addFolder); + notifyFolderUpdate(updateFolder); + notifyImageAdd(addImages); + } + + private void callDataRemoved(List images) { + List updateFolder = new ArrayList<>(); + List removeFolder = new ArrayList<>(); + List removeImages = new ArrayList<>(); + + final Map folderSource = mFolderSource; + for (Image image : images) { + File folderFile = new File(image.getPath()).getParentFile(); + String folderPath = folderFile.getAbsolutePath().toLowerCase(); + if (folderSource.containsKey(folderPath)) { + ImageFolder folder = folderSource.get(folderPath); + List folderImages = folder.getImages(); + if (folderImages.contains(image)) { + // only remove option + folderImages.remove(image); + if (folderImages.size() == 0) { + // Remove + folderSource.remove(folderPath); + removeFolder.add(folder); + } else { + // Update data remove + if (!updateFolder.contains(folder)) { + updateFolder.add(folder); + } + } + } + } + + if (mRegisterKey.equals(folderPath)) { + removeImages.add(image); + } + } + + notifyFolderUpdate(updateFolder); + notifyFolderRemove(removeFolder); + notifyImageRemove(removeImages); + } + + @Override + public void onLoaderReset(Loader loader) { + + } + + private void notifyFolderUpdate(List items) { + if (items.size() == 0) + return; + mFolderCallback.onFolderUpdated(items); + } + + private void notifyFolderAdd(List items) { + if (items.size() == 0) + return; + mFolderCallback.onFolderAdded(items); + } + + private void notifyFolderRemove(List items) { + if (items.size() == 0) + return; + mFolderCallback.onFolderRemoved(items); + } + + + private void notifyImageAdd(List items) { + if (items.size() == 0) + return; + mImageCallback.onPictureAdded(items); + } + + private void notifyImageRemove(List items) { + if (items.size() == 0) + return; + mImageCallback.onPictureRemoved(items); + } + + public void selectFolder(ImageFolder folder) { + this.mRegisterKey = folder.getPath().toLowerCase(); + } + + public interface PictureSourceCallback { + void onPictureRemoved(List images); + + void onPictureAdded(List images); + } + + public interface FolderSourceCallback { + void onFolderRemoved(List images); + + void onFolderAdded(List images); + + void onFolderUpdated(List images); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/PreviewerViewPager.java b/app/src/main/java/net/oschina/app/improve/media/PreviewerViewPager.java new file mode 100644 index 0000000000000000000000000000000000000000..06efcaf19bade47ec3a24fb0e292f08c067d16f9 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/PreviewerViewPager.java @@ -0,0 +1,69 @@ +package net.oschina.app.improve.media; + +import android.content.Context; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; + +/** + * 适配ImagePreviewerView的使用 + * Created by thanatos on 16/8/5. + */ +public class PreviewerViewPager extends ViewPager { + + private boolean isInterceptable = false; + private boolean isTransition = false; + private int mScrollState = SCROLL_STATE_IDLE; + + public PreviewerViewPager(Context context) { + this(context, null); + } + + public PreviewerViewPager(Context context, AttributeSet attrs) { + super(context, attrs); + addOnPageChangeListener(new PageChangeListener()); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (mScrollState != SCROLL_STATE_IDLE) { + return super.onInterceptTouchEvent(ev); + } + + // 移动到边界改变拦截方式时 + if (isTransition) { + int action = ev.getAction(); + ev.setAction(MotionEvent.ACTION_DOWN); + super.onInterceptTouchEvent(ev); + ev.setAction(action); + isTransition = false; + } + + boolean b = false; + + int action = ev.getAction(); + + if (action == MotionEvent.ACTION_DOWN) { + isInterceptable = false; + } + + if (action != MotionEvent.ACTION_MOVE || isInterceptable) { + b = super.onInterceptTouchEvent(ev); + } + + return isInterceptable && b; + } + + public void isInterceptable(boolean b) { + if (!isInterceptable && b) isTransition = true; + this.isInterceptable = b; + } + + private class PageChangeListener extends SimpleOnPageChangeListener { + + @Override + public void onPageScrollStateChanged(int state) { + mScrollState = state; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/SelectFragment.java b/app/src/main/java/net/oschina/app/improve/media/SelectFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..ef28b2b2bcd371b36716a0925a8042b922bfcf89 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/SelectFragment.java @@ -0,0 +1,503 @@ +package net.oschina.app.improve.media; + +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.MediaStore; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.FileProvider; +import android.support.v4.content.Loader; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.Toast; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.media.adapter.ImageAdapter; +import net.oschina.app.improve.media.adapter.ImageFolderAdapter; +import net.oschina.app.improve.media.bean.Image; +import net.oschina.app.improve.media.bean.ImageFolder; +import net.oschina.app.improve.media.config.ImageLoaderListener; +import net.oschina.app.improve.media.config.SelectOptions; +import net.oschina.app.improve.media.contract.SelectImageContract; +import net.oschina.app.improve.media.crop.CropActivity; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.util.TDevice; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 图片选择库实现界面 + * Created by huanghaibin_dev + * on 2016/7/13. + *

    + * Changed by qiujuer + * on 2016/09/01 + */ +public class SelectFragment extends BaseFragment implements SelectImageContract.View, View.OnClickListener, + ImageLoaderListener, BaseRecyclerAdapter.OnItemClickListener { + @Bind(R.id.rv_image) + RecyclerView mContentView; + @Bind(R.id.btn_title_select) + Button mSelectFolderView; + @Bind(R.id.iv_title_select) + ImageView mSelectFolderIcon; + @Bind(R.id.toolbar) + View mToolbar; + @Bind(R.id.btn_done) + Button mDoneView; + @Bind(R.id.btn_preview) + Button mPreviewView; + + @Bind(R.id.error_layout) + EmptyLayout mErrorLayout; + + private ImageFolderPopupWindow mFolderPopupWindow; + private ImageFolderAdapter mImageFolderAdapter; + private ImageAdapter mImageAdapter; + + private List mSelectedImage; + + private String mCamImageName; + private LoaderListener mCursorLoader = new LoaderListener(); + + private SelectImageContract.Operator mOperator; + + private static SelectOptions mOption; + + public static SelectFragment newInstance(SelectOptions options) { + mOption = options; + return new SelectFragment(); + } + + @Override + public void onAttach(Context context) { + this.mOperator = (SelectImageContract.Operator) context; + this.mOperator.setDataView(this); + super.onAttach(context); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_select_image; + } + + @OnClick({R.id.btn_preview, R.id.icon_back, R.id.btn_title_select, R.id.btn_done}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.icon_back: + mOperator.onBack(); + break; + case R.id.btn_preview: + if (mSelectedImage.size() > 0) { + ImageGalleryActivity.show(getActivity(), Util.toArray(mSelectedImage), 0, false); + } + break; + case R.id.btn_title_select: + showPopupFolderList(); + break; + case R.id.btn_done: + onSelectComplete(); + break; + } + } + + @Override + protected void initWidget(View view) { + if(mOption == null){ + getActivity().finish(); + return; + } + mContentView.setLayoutManager(new GridLayoutManager(getActivity(), 4)); + mContentView.addItemDecoration(new SpaceGridItemDecoration((int) TDevice.dipToPx(getResources(), 1))); + mImageAdapter = new ImageAdapter(getContext(), this); + mImageAdapter.setSingleSelect(mOption.getSelectCount() <= 1); + mRoot.findViewById(R.id.lay_button).setVisibility(mOption.getSelectCount() == 1 ? View.GONE : View.VISIBLE); + mImageFolderAdapter = new ImageFolderAdapter(getActivity()); + mImageFolderAdapter.setLoader(this); + mContentView.setAdapter(mImageAdapter); + mContentView.setItemAnimator(null); + mImageAdapter.setOnItemClickListener(this); + mErrorLayout.setOnLayoutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + getLoaderManager().initLoader(0, null, mCursorLoader); + } + }); + } + + @Override + protected void initData() { + if(mOption == null){ + getActivity().finish(); + return; + } + mSelectedImage = new ArrayList<>(); + + if (mOption.getSelectCount() > 1 && mOption.getSelectedImages() != null) { + List images = mOption.getSelectedImages(); + for (String s : images) { + // checkShare file exists + if (s != null && new File(s).exists()) { + Image image = new Image(); + image.setSelect(true); + image.setPath(s); + mSelectedImage.add(image); + } + } + } + getLoaderManager().initLoader(0, null, mCursorLoader); + } + + + @Override + public void onItemClick(int position, long itemId) { + if (mOption.isHasCam()) { + if (position != 0) { + handleSelectChange(position); + } else { + if (mSelectedImage.size() < mOption.getSelectCount()) { + mOperator.requestCamera(); + } else { + Toast.makeText(getActivity(), "最多只能选择 " + mOption.getSelectCount() + " 张图片", Toast.LENGTH_SHORT).show(); + } + } + } else { + handleSelectChange(position); + } + } + + private void handleSelectSizeChange(int size) { + if (size > 0) { + mPreviewView.setEnabled(true); + mDoneView.setEnabled(true); + mDoneView.setText(String.format("%s(%s)", getText(R.string.image_select_opt_done), size)); + } else { + mPreviewView.setEnabled(false); + mDoneView.setEnabled(false); + mDoneView.setText(getText(R.string.image_select_opt_done)); + } + } + + private void handleSelectChange(int position) { + Image image = mImageAdapter.getItem(position); + if(image == null) + return; + //如果是多选模式 + final int selectCount = mOption.getSelectCount(); + if (selectCount > 1) { + if (image.isSelect()) { + image.setSelect(false); + mSelectedImage.remove(image); + mImageAdapter.updateItem(position); + } else { + if (mSelectedImage.size() == selectCount) { + Toast.makeText(getActivity(), "最多只能选择 " + selectCount + " 张照片", Toast.LENGTH_SHORT).show(); + } else { + image.setSelect(true); + mSelectedImage.add(image); + mImageAdapter.updateItem(position); + } + } + handleSelectSizeChange(mSelectedImage.size()); + } else { + mSelectedImage.add(image); + handleResult(); + } + } + + private void handleResult() { + if (mSelectedImage.size() != 0) { + if (mOption.isCrop()) { + List selectedImage = mOption.getSelectedImages(); + selectedImage.clear(); + selectedImage.add(mSelectedImage.get(0).getPath()); + mSelectedImage.clear(); + CropActivity.show(this, mOption); + } else { + mOption.getCallback().doSelected(Util.toArray(mSelectedImage)); + getActivity().finish(); + } + } + } + + /** + * 完成选择 + */ + public void onSelectComplete() { + handleResult(); + } + + /** + * 申请相机权限成功 + */ + @Override + public void onOpenCameraSuccess() { + toOpenCamera(); + } + + + @Override + public void onCameraPermissionDenied() { + + } + + /** + * 创建弹出的相册 + */ + private void showPopupFolderList() { + if (mFolderPopupWindow == null) { + ImageFolderPopupWindow popupWindow = new ImageFolderPopupWindow(getActivity(), new ImageFolderPopupWindow.Callback() { + @Override + public void onSelect(ImageFolderPopupWindow popupWindow, ImageFolder model) { + addImagesToAdapter(model.getImages()); + mContentView.scrollToPosition(0); + popupWindow.dismiss(); + mSelectFolderView.setText(model.getName()); + } + + @Override + public void onDismiss() { + mSelectFolderIcon.setImageResource(R.mipmap.ic_arrow_bottom); + } + + @Override + public void onShow() { + mSelectFolderIcon.setImageResource(R.mipmap.ic_arrow_top); + } + }); + popupWindow.setAdapter(mImageFolderAdapter); + mFolderPopupWindow = popupWindow; + } + mFolderPopupWindow.showAsDropDown(mToolbar); + } + + /** + * 打开相机 + */ + private void toOpenCamera() { + // 判断是否挂载了SD卡 + mCamImageName = null; + String savePath = ""; + if (Util.hasSDCard()) { + savePath = Util.getCameraPath(); + File saveDir = new File(savePath); + if (!saveDir.exists()) { + saveDir.mkdirs(); + } + } + + // 没有挂载SD卡,无法保存文件 + if (TextUtils.isEmpty(savePath)) { + Toast.makeText(getActivity(), "无法保存照片,请检查SD卡是否挂载", Toast.LENGTH_LONG).show(); + return; + } + + mCamImageName = Util.getSaveImageFullName(); + File out = new File(savePath, mCamImageName); + + /** + * android N 系统适配 + */ + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + Uri uri; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + uri = FileProvider.getUriForFile(getContext(), "net.oschina.app.provider", out); + } else { + uri = Uri.fromFile(out); + } + intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + startActivityForResult(intent, + 0x03); + } + + /** + * 拍照完成通知系统添加照片 + * + * @param requestCode requestCode + * @param resultCode resultCode + * @param data data + */ + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == AppCompatActivity.RESULT_OK) { + switch (requestCode) { + case 0x03: + if (mCamImageName == null) return; + Uri localUri = Uri.fromFile(new File(Util.getCameraPath() + mCamImageName)); + Intent localIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, localUri); + getActivity().sendBroadcast(localIntent); + break; + case 0x04: + if (data == null) return; + mOption.getCallback().doSelected(new String[]{data.getStringExtra("crop_path")}); + getActivity().finish(); + break; + } + } + } + + @Override + public void displayImage(final ImageView iv, final String path) { + getImgLoader().load(path) + .asBitmap() + .centerCrop() + .error(R.mipmap.ic_split_graph) + .into(iv); + } + + private class LoaderListener implements LoaderManager.LoaderCallbacks { + private final String[] IMAGE_PROJECTION = { + MediaStore.Images.Media.DATA, + MediaStore.Images.Media.DISPLAY_NAME, + MediaStore.Images.Media.DATE_ADDED, + MediaStore.Images.Media._ID, + MediaStore.Images.Media.MINI_THUMB_MAGIC, + MediaStore.Images.Media.BUCKET_DISPLAY_NAME}; + + @Override + public Loader onCreateLoader(int id, Bundle args) { + if (id == 0) { + //数据库光标加载器 + return new CursorLoader(getContext(), + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION, + null, null, IMAGE_PROJECTION[2] + " DESC"); + } + return null; + } + + @Override + public void onLoadFinished(Loader loader, final Cursor data) { + if (data != null) { + + final ArrayList images = new ArrayList<>(); + final List imageFolders = new ArrayList<>(); + + final ImageFolder defaultFolder = new ImageFolder(); + defaultFolder.setName("全部照片"); + defaultFolder.setPath(""); + imageFolders.add(defaultFolder); + + int count = data.getCount(); + if (count > 0) { + data.moveToFirst(); + do { + String path = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[0])); + String name = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[1])); + long dateTime = data.getLong(data.getColumnIndexOrThrow(IMAGE_PROJECTION[2])); + int id = data.getInt(data.getColumnIndexOrThrow(IMAGE_PROJECTION[3])); + String thumbPath = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[4])); + String bucket = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[5])); + + Image image = new Image(); + image.setPath(path); + image.setName(name); + image.setDate(dateTime); + image.setId(id); + image.setThumbPath(thumbPath); + image.setFolderName(bucket); + + images.add(image); + + //如果是新拍的照片 + if (mCamImageName != null && mCamImageName.equals(image.getName())) { + image.setSelect(true); + mSelectedImage.add(image); + } + + //如果是被选中的图片 + if (mSelectedImage.size() > 0) { + for (Image i : mSelectedImage) { + if (i.getPath().equals(image.getPath())) { + image.setSelect(true); + } + } + } + + File imageFile = new File(path); + File folderFile = imageFile.getParentFile(); + ImageFolder folder = new ImageFolder(); + folder.setName(folderFile.getName()); + folder.setPath(folderFile.getAbsolutePath()); + if (!imageFolders.contains(folder)) { + folder.getImages().add(image); + folder.setAlbumPath(image.getPath());//默认相册封面 + imageFolders.add(folder); + } else { + // 更新 + ImageFolder f = imageFolders.get(imageFolders.indexOf(folder)); + f.getImages().add(image); + } + + + } while (data.moveToNext()); + } + addImagesToAdapter(images); + defaultFolder.getImages().addAll(images); + if (mOption.isHasCam()) { + defaultFolder.setAlbumPath(images.size() > 1 ? images.get(1).getPath() : null); + } else { + defaultFolder.setAlbumPath(images.size() > 0 ? images.get(0).getPath() : null); + } + mImageFolderAdapter.resetItem(imageFolders); + + //删除掉不存在的,在于用户选择了相片,又去相册删除 + if (mSelectedImage.size() > 0) { + List rs = new ArrayList<>(); + for (Image i : mSelectedImage) { + File f = new File(i.getPath()); + if (!f.exists()) { + rs.add(i); + } + } + mSelectedImage.removeAll(rs); + } + + // If add new mCamera picture, and we only need one picture, we result it. + if (mOption.getSelectCount() == 1 && mCamImageName != null) { + handleResult(); + } + + handleSelectSizeChange(mSelectedImage.size()); + mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + } + } + + @Override + public void onLoaderReset(Loader loader) { + + } + } + + private void addImagesToAdapter(ArrayList images) { + mImageAdapter.clear(); + if (mOption.isHasCam()) { + Image cam = new Image(); + mImageAdapter.addItem(cam); + } + mImageAdapter.addAll(images); + } + + @Override + public void onDestroy() { + mOption = null; + super.onDestroy(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/SelectImageActivity.java b/app/src/main/java/net/oschina/app/improve/media/SelectImageActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..c6a35d7e570c078d5530f319dd9bd7c41f3315a6 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/SelectImageActivity.java @@ -0,0 +1,170 @@ +package net.oschina.app.improve.media; + +import android.Manifest; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseBackActivity; +import net.oschina.app.improve.media.config.SelectOptions; +import net.oschina.app.improve.media.contract.SelectImageContract; +import net.oschina.app.improve.utils.DialogHelper; + +import java.util.List; + +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * Created by huanghaibin_dev + * on 2016/7/13. + *

    + * Changed by qiujuer + * on 2016/09/01 + */ +@SuppressWarnings("All") +public class SelectImageActivity extends BaseBackActivity implements EasyPermissions.PermissionCallbacks, SelectImageContract.Operator { + private static final int RC_CAMERA_PERM = 0x03; + private static final int RC_EXTERNAL_STORAGE = 0x04; + public static final String KEY_CONFIG = "config"; + + private static SelectOptions mOption; + private SelectImageContract.View mView; + + public static void show(Context context, SelectOptions options) { + mOption = options; + context.startActivity(new Intent(context, SelectImageActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_select_image; + } + + @Override + protected void initWidget() { + super.initWidget(); + setSwipeBackEnable(false); + setStatusBarDarkMode(); + requestExternalStorage(); + } + + @AfterPermissionGranted(RC_CAMERA_PERM) + @Override + public void requestCamera() { + if (EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA)) { + if (mView != null) { + mView.onOpenCameraSuccess(); + } + } else { + EasyPermissions.requestPermissions(this, "", RC_CAMERA_PERM, Manifest.permission.CAMERA); + } + } + + @AfterPermissionGranted(RC_EXTERNAL_STORAGE) + @Override + public void requestExternalStorage() { + if (EasyPermissions.hasPermissions(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { + if (mView == null) { + handleView(); + } + } else { + EasyPermissions.requestPermissions(this, "", RC_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE); + } + } + + @Override + public void onBack() { + onSupportNavigateUp(); + } + + @Override + public void setDataView(SelectImageContract.View view) { + mView = view; + } + + @Override + protected void onDestroy() { + mOption = null; + super.onDestroy(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + + @Override + public boolean shouldShowRequestPermissionRationale(String permission) { + return false; + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + if (requestCode == RC_EXTERNAL_STORAGE) { + removeView(); + DialogHelper.getConfirmDialog(this, "", "没有权限, 你需要去设置中开启读取手机存储权限.", "去设置", "取消", false, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + finish(); + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }).show(); + } else { + if (mView != null) + mView.onCameraPermissionDenied(); + DialogHelper.getConfirmDialog(this, "", "没有权限, 你需要去设置中开启相机权限.", "去设置", "取消", false, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }).show(); + } + } + + private void removeView() { + SelectImageContract.View view = mView; + if (view == null) + return; + try { + getSupportFragmentManager() + .beginTransaction() + .remove((Fragment) view) + .commitNowAllowingStateLoss(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void handleView() { + try { + //Fragment fragment = Fragment.instantiate(this, SelectFragment.class.getName()); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.fl_content, SelectFragment.newInstance(mOption)) + .commitNowAllowingStateLoss(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/SpaceGridItemDecoration.java b/app/src/main/java/net/oschina/app/improve/media/SpaceGridItemDecoration.java new file mode 100644 index 0000000000000000000000000000000000000000..d023ace9155e87fde54d0800a1a84dde61e5220a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/SpaceGridItemDecoration.java @@ -0,0 +1,26 @@ +package net.oschina.app.improve.media; + +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Created by huanghaibin + * on 16-5-9. + */ +public class SpaceGridItemDecoration extends RecyclerView.ItemDecoration { + private int space; + + public SpaceGridItemDecoration(int space) { + this.space = space; + } + + @Override + public void getItemOffsets(Rect outRect, View view, + RecyclerView parent, RecyclerView.State state) { + outRect.left = space; + outRect.right = space; + outRect.bottom = space; + outRect.top = space; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/Util.java b/app/src/main/java/net/oschina/app/improve/media/Util.java new file mode 100644 index 0000000000000000000000000000000000000000..ee8c1416c8d4a351afbb9f9ab07a5f4e344eeb20 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/Util.java @@ -0,0 +1,108 @@ +package net.oschina.app.improve.media; + +import android.content.Context; +import android.os.Environment; +import android.view.Display; +import android.view.WindowManager; + +import net.oschina.app.improve.media.bean.Image; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 选择图片库相关工具类 + */ +@SuppressWarnings("All") +public class Util { + + public static boolean hasSDCard() { + String status = Environment.getExternalStorageState(); + if (!status.equals(Environment.MEDIA_MOUNTED)) { + return false; + } + return true; + } + + public static String getCameraPath() { + return Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera/";// filePath:/sdcard/ + } + + public static String getSaveImageFullName() { + return "IMG_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".jpg";// 照片命名 + } + + public static ArrayList toArrayList(List images) { + ArrayList strings = new ArrayList<>(); + for (Image i : images) { + strings.add(i.getPath()); + } + return strings; + } + + public static String[] toArray(List images) { + if (images == null) + return null; + int len = images.size(); + if (len == 0) + return null; + + String[] strings = new String[len]; + int i = 0; + for (Image image : images) { + strings[i] = image.getPath(); + i++; + } + return strings; + } + + /** + * 获得屏幕的宽度 + * + * @param context context + * @return width + */ + public static int getScreenWidth(Context context) { + WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = manager.getDefaultDisplay(); + return display.getWidth(); + } + + /** + * 获得屏幕的高度 + * + * @param context context + * @return height + */ + public static int getScreenHeight(Context context) { + WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = manager.getDefaultDisplay(); + return display.getHeight(); + } + + /** + * dp转px + * + * @param context context + * @param dpValue dp + * @return px + */ + public static int dipTopx(Context context, float dpValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + /** + * px转dp + * + * @param context context + * @param pxValue px + * @return dp + */ + public static float pxTodip(Context context, float pxValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return pxValue / scale + 0.5f; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/adapter/ImageAdapter.java b/app/src/main/java/net/oschina/app/improve/media/adapter/ImageAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..59332209a4c819c8d50ec79c392fed25d1fff888 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/adapter/ImageAdapter.java @@ -0,0 +1,92 @@ +package net.oschina.app.improve.media.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.media.bean.Image; +import net.oschina.app.improve.media.config.ImageLoaderListener; + + +/** + * 图片列表界面适配器 + */ +public class ImageAdapter extends BaseRecyclerAdapter { + private ImageLoaderListener loader; + private boolean isSingleSelect; + + public ImageAdapter(Context context, ImageLoaderListener loader) { + super(context, NEITHER); + this.loader = loader; + } + + public void setSingleSelect(boolean singleSelect) { + isSingleSelect = singleSelect; + } + + @Override + public int getItemViewType(int position) { + Image image = getItem(position); + if (image.getId() == 0) + return 0; + return 1; + } + + @Override + public void onViewRecycled(RecyclerView.ViewHolder holder) { + if (holder instanceof ImageViewHolder) { + ImageViewHolder h = (ImageViewHolder) holder; + Glide.clear(h.mImageView); + } + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + if (type == 0) + return new CamViewHolder(mInflater.inflate(R.layout.item_list_cam, parent, false)); + return new ImageViewHolder(mInflater.inflate(R.layout.item_list_image, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Image item, int position) { + if (item.getId() != 0) { + ImageViewHolder h = (ImageViewHolder) holder; + h.mCheckView.setSelected(item.isSelect()); + h.mMaskView.setVisibility(item.isSelect() ? View.VISIBLE : View.GONE); + + // Show gif mask + h.mGifMask.setVisibility(item.getPath().toLowerCase().endsWith("gif") ? + View.VISIBLE : View.GONE); + + loader.displayImage(h.mImageView, item.getPath()); + h.mCheckView.setVisibility(isSingleSelect ? View.GONE : View.VISIBLE); + } + } + + private static class CamViewHolder extends RecyclerView.ViewHolder { + CamViewHolder(View itemView) { + super(itemView); + } + } + + private static class ImageViewHolder extends RecyclerView.ViewHolder { + ImageView mImageView; + ImageView mCheckView; + ImageView mGifMask; + View mMaskView; + + ImageViewHolder(View itemView) { + super(itemView); + mImageView = (ImageView) itemView.findViewById(R.id.iv_image); + mCheckView = (ImageView) itemView.findViewById(R.id.cb_selected); + mMaskView = itemView.findViewById(R.id.lay_mask); + mGifMask = (ImageView) itemView.findViewById(R.id.iv_is_gif); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/adapter/ImageFolderAdapter.java b/app/src/main/java/net/oschina/app/improve/media/adapter/ImageFolderAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..e17d3cdbf25310cf46af737af93be13ab585834f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/adapter/ImageFolderAdapter.java @@ -0,0 +1,61 @@ +package net.oschina.app.improve.media.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.media.bean.ImageFolder; +import net.oschina.app.improve.media.config.ImageLoaderListener; + + +/** + * Created by huanghaibin_dev + * on 2016/7/13. + *

    + * Changed by qiujuer + * on 2016/09/01 + */ + +public class ImageFolderAdapter extends BaseRecyclerAdapter { + private ImageLoaderListener loader; + + public ImageFolderAdapter(Context context) { + super(context, NEITHER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new FolderViewHolder(mInflater.inflate(R.layout.item_list_folder, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, ImageFolder item, int position) { + FolderViewHolder h = (FolderViewHolder) holder; + h.tv_name.setText(item.getName()); + h.tv_size.setText(String.format("(%s)", item.getImages().size())); + if (loader != null) { + loader.displayImage(h.iv_image, item.getAlbumPath()); + } + } + + public void setLoader(ImageLoaderListener loader) { + this.loader = loader; + } + + private static class FolderViewHolder extends RecyclerView.ViewHolder { + ImageView iv_image; + TextView tv_name, tv_size; + + public FolderViewHolder(View itemView) { + super(itemView); + iv_image = (ImageView) itemView.findViewById(R.id.iv_folder); + tv_name = (TextView) itemView.findViewById(R.id.tv_folder_name); + tv_size = (TextView) itemView.findViewById(R.id.tv_size); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/bean/Image.java b/app/src/main/java/net/oschina/app/improve/media/bean/Image.java new file mode 100644 index 0000000000000000000000000000000000000000..a881a9c01931bce05b3540d9f4997e0e5eeb383f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/bean/Image.java @@ -0,0 +1,82 @@ +package net.oschina.app.improve.media.bean; + +import java.io.Serializable; + +/** + * Created by huanghaibin_dev + * on 2016/7/11. + */ + +public class Image implements Serializable { + private int id; + private String path; + private String thumbPath; + private boolean isSelect; + private String folderName; + private String name; + private long date; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getThumbPath() { + return thumbPath; + } + + public void setThumbPath(String thumbPath) { + this.thumbPath = thumbPath; + } + + public boolean isSelect() { + return isSelect; + } + + public void setSelect(boolean select) { + isSelect = select; + } + + public String getFolderName() { + return folderName; + } + + public void setFolderName(String folderName) { + this.folderName = folderName; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getDate() { + return date; + } + + public void setDate(long date) { + this.date = date; + } + + @Override + public boolean equals(Object o) { + if (o instanceof Image) { + return this.path.equals(((Image) o).getPath()); + } + return false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/bean/ImageFolder.java b/app/src/main/java/net/oschina/app/improve/media/bean/ImageFolder.java new file mode 100644 index 0000000000000000000000000000000000000000..85f94627337a4eb5cd0295e61f76494fc6b33331 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/bean/ImageFolder.java @@ -0,0 +1,54 @@ +package net.oschina.app.improve.media.bean; + +import java.io.Serializable; +import java.util.ArrayList; + +/** + * Created by huanghaibin_dev + * on 2016/7/11. + */ +public class ImageFolder implements Serializable { + private String name; + private String path; + private String albumPath; + private ArrayList images = new ArrayList<>(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public ArrayList getImages() { + return images; + } + + public String getAlbumPath() { + return albumPath; + } + + public void setAlbumPath(String albumPath) { + this.albumPath = albumPath; + } + + @Override + public boolean equals(Object o) { + if (o != null && o instanceof ImageFolder) { + if (((ImageFolder) o).getPath() == null && path != null) + return false; + String oPath = ((ImageFolder) o).getPath().toLowerCase(); + return oPath.equals(this.path.toLowerCase()); + } + return false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/config/ImageLoaderListener.java b/app/src/main/java/net/oschina/app/improve/media/config/ImageLoaderListener.java new file mode 100644 index 0000000000000000000000000000000000000000..14f324d3dd84feea0429d5f1aca32afe2753b1e9 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/config/ImageLoaderListener.java @@ -0,0 +1,12 @@ +package net.oschina.app.improve.media.config; + +import android.widget.ImageView; + +/** + * 暴露一个图片加载器 + * Created by huanghaibin_dev + * on 2016/7/13. + */ +public interface ImageLoaderListener { + void displayImage(ImageView iv, String path); +} diff --git a/app/src/main/java/net/oschina/app/improve/media/config/SelectOptions.java b/app/src/main/java/net/oschina/app/improve/media/config/SelectOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..3053984b260a927775de121ded3eedf4473576a9 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/config/SelectOptions.java @@ -0,0 +1,121 @@ +package net.oschina.app.improve.media.config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Created by haibin + * on 2016/12/5. + */ + +public final class SelectOptions { + private boolean isCrop; + private int mCropWidth, mCropHeight; + private Callback mCallback; + private boolean hasCam; + private int mSelectCount; + private List mSelectedImages; + + private SelectOptions() { + + } + + public boolean isCrop() { + return isCrop; + } + + public int getCropWidth() { + return mCropWidth; + } + + public int getCropHeight() { + return mCropHeight; + } + + public Callback getCallback() { + return mCallback; + } + + public boolean isHasCam() { + return hasCam; + } + + public int getSelectCount() { + return mSelectCount; + } + + public List getSelectedImages() { + return mSelectedImages; + } + + public static class Builder { + private boolean isCrop; + private int cropWidth, cropHeight; + private Callback callback; + private boolean hasCam; + private int selectCount; + private List selectedImages; + + public Builder() { + selectCount = 1; + hasCam = true; + selectedImages = new ArrayList<>(); + } + + public Builder setCrop(int cropWidth, int cropHeight) { + if (cropWidth <= 0 || cropHeight <= 0) + throw new IllegalArgumentException("cropWidth or cropHeight mast be greater than 0 "); + this.isCrop = true; + this.cropWidth = cropWidth; + this.cropHeight = cropHeight; + return this; + } + + public Builder setCallback(Callback callback) { + this.callback = callback; + return this; + } + + public Builder setHasCam(boolean hasCam) { + this.hasCam = hasCam; + return this; + } + + public Builder setSelectCount(int selectCount) { + if (selectCount <= 0) + throw new IllegalArgumentException("selectCount mast be greater than 0 "); + this.selectCount = selectCount; + return this; + } + + public Builder setSelectedImages(List selectedImages) { + if (selectedImages == null || selectedImages.size() == 0) return this; + this.selectedImages.addAll(selectedImages); + return this; + } + + public Builder setSelectedImages(String[] selectedImages) { + if (selectedImages == null || selectedImages.length == 0) return this; + if (this.selectedImages == null) this.selectedImages = new ArrayList<>(); + this.selectedImages.addAll(Arrays.asList(selectedImages)); + return this; + } + + public SelectOptions build() { + SelectOptions options = new SelectOptions(); + options.hasCam = hasCam; + options.isCrop = isCrop; + options.mCropHeight = cropHeight; + options.mCropWidth = cropWidth; + options.mCallback = callback; + options.mSelectCount = selectCount; + options.mSelectedImages = selectedImages; + return options; + } + } + + public interface Callback { + void doSelected(String[] images); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/contract/SelectImageContract.java b/app/src/main/java/net/oschina/app/improve/media/contract/SelectImageContract.java new file mode 100644 index 0000000000000000000000000000000000000000..a316e7bd7aace122cd27d9a7aa6fe2924f1d954f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/contract/SelectImageContract.java @@ -0,0 +1,25 @@ +package net.oschina.app.improve.media.contract; + +/** + * 图片选择器建立契约关系,将权限操作放在Activity,具体数据放在Fragment + * Created by huanghaibin_dev + * on 2016/7/15. + */ +public interface SelectImageContract { + interface Operator { + void requestCamera(); + + void requestExternalStorage(); + + void onBack(); + + void setDataView(View view); + } + + interface View { + + void onOpenCameraSuccess(); + + void onCameraPermissionDenied(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/crop/CropActivity.java b/app/src/main/java/net/oschina/app/improve/media/crop/CropActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..d79505b4318acd5267af134da46f79cb2c637369 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/crop/CropActivity.java @@ -0,0 +1,97 @@ +package net.oschina.app.improve.media.crop; + +import android.content.Intent; +import android.graphics.Bitmap; +import android.support.v4.app.Fragment; +import android.view.View; +import android.view.WindowManager; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.media.config.SelectOptions; +import net.oschina.common.utils.StreamUtil; + +import java.io.FileOutputStream; + +import butterknife.OnClick; + +/** + * Created by haibin + * on 2016/12/2. + */ + +public class CropActivity extends BaseActivity implements View.OnClickListener { + private CropLayout mCropLayout; + private static SelectOptions mOption; + + public static void show(Fragment fragment, SelectOptions options) { + Intent intent = new Intent(fragment.getActivity(), CropActivity.class); + mOption = options; + fragment.startActivityForResult(intent, 0x04); + } + + @Override + protected int getContentView() { + return R.layout.activity_crop; + } + + @Override + protected void initWidget() { + super.initWidget(); + setTitle(""); + getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); + mCropLayout = (CropLayout) findViewById(R.id.cropLayout); + } + + @Override + protected void initData() { + super.initData(); + setSwipeBackEnable(false); + String url = mOption.getSelectedImages().get(0); + getImageLoader().load(url) + .fitCenter() + .into(mCropLayout.getImageView()); + + mCropLayout.setCropWidth(mOption.getCropWidth()); + mCropLayout.setCropHeight(mOption.getCropHeight()); + mCropLayout.start(); + } + + @OnClick({R.id.tv_crop, R.id.tv_cancel}) + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.tv_crop: + Bitmap bitmap = null; + FileOutputStream os = null; + try { + bitmap = mCropLayout.cropBitmap(); + String path = getFilesDir() + "/crop.jpg"; + os = new FileOutputStream(path); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + os.flush(); + os.close(); + + Intent intent = new Intent(); + intent.putExtra("crop_path", path); + setResult(RESULT_OK, intent); + finish(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (bitmap != null) bitmap.recycle(); + StreamUtil.close(os); + } + break; + case R.id.tv_cancel: + finish(); + break; + } + } + + @Override + protected void onDestroy() { + mOption = null; + super.onDestroy(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/crop/CropDrawable.java b/app/src/main/java/net/oschina/app/improve/media/crop/CropDrawable.java new file mode 100644 index 0000000000000000000000000000000000000000..c15a81f1acbd09635fd26048d56d544539aa6112 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/crop/CropDrawable.java @@ -0,0 +1,151 @@ +package net.oschina.app.improve.media.crop; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +import net.oschina.app.improve.media.Util; + +/** + * Created by haibin + * on 2016/12/1. + */ +@SuppressWarnings("ALL") +public class CropDrawable extends Drawable { + private Context mContext; + private int offset = 50; + + private Paint mCornerPaint = new Paint(); + private Paint mLinePaint = new Paint(); + private Paint mNineLinePaint = new Paint(); + + private int mCropWidth = 800, mCropHeight = 800; + + private static final int RADIUS = 20; + + private int mLeft, mRight, mTop, mBottom; + + public CropDrawable(Context mContext) { + this.mContext = mContext; + initPaint(); + } + + private void initPaint() { + mLinePaint.setColor(Color.WHITE); + mLinePaint.setAntiAlias(true); + mLinePaint.setStrokeWidth(2); + mLinePaint.setStyle(Paint.Style.STROKE); + + mNineLinePaint.setColor(Color.WHITE); + mNineLinePaint.setAntiAlias(true); + mNineLinePaint.setStrokeWidth(1); + mNineLinePaint.setStyle(Paint.Style.STROKE); + + mCornerPaint.setColor(Color.WHITE); + mCornerPaint.setAntiAlias(true); + mCornerPaint.setStrokeWidth(8); + mCornerPaint.setStyle(Paint.Style.FILL); + } + + @Override + public void draw(Canvas canvas) { + int width = Util.getScreenWidth(mContext); + int height = Util.getScreenHeight(mContext); + mLeft = (width - mCropWidth) / 2; + mTop = (height - mCropHeight) / 2; + mRight = (width + mCropWidth) / 2; + mBottom = (height + mCropHeight) / 2; + Rect rect = new Rect(mLeft, mTop, mRight, mBottom); + canvas.drawRect(rect, mLinePaint); + //左上 + canvas.drawLine(mLeft, mTop, mLeft, mTop + 50, mCornerPaint); + canvas.drawLine(mLeft - 4, mTop, mLeft + 50, mTop, mCornerPaint); + + //右上 + canvas.drawLine(mRight, mTop, mRight, mTop + 50, mCornerPaint); + canvas.drawLine(mRight - 50, mTop, mRight + 4, mTop, mCornerPaint); + + //左下 + canvas.drawLine(mLeft, mBottom, mLeft + 50, mBottom, mCornerPaint); + canvas.drawLine(mLeft, mBottom - 50, mLeft, mBottom + 4, mCornerPaint); + + //右下 + canvas.drawLine(mRight, mBottom, mRight, mBottom - 50, mCornerPaint); + canvas.drawLine(mRight - 50, mBottom, mRight + 4, mBottom, mCornerPaint); + + int index = canvas.save(); + canvas.clipRect(rect); + //画九宫格 + int vAvg = mCropWidth / 3; + int hAvg = mCropHeight / 3; + canvas.drawLine(mLeft + vAvg, mTop, mLeft + vAvg, mBottom, mNineLinePaint); + canvas.drawLine(mLeft + vAvg * 2, mTop, mLeft + vAvg * 2, mBottom, mNineLinePaint); + + canvas.drawLine(mLeft, mTop + hAvg, mRight, mTop + hAvg, mNineLinePaint); + canvas.drawLine(mLeft, mTop + hAvg * 2, mRight, mTop + hAvg * 2, mNineLinePaint); + + canvas.restoreToCount(index); + } + + @Override + public void setAlpha(int alpha) { + + } + + public void offset(int x, int y) { + getBounds().offset(x, y); + } + + @Override + public void setBounds(Rect bounds) { + super.setBounds(new Rect(mLeft, mTop, mRight, mBottom)); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + + } + + @Override + public int getOpacity() { + return PixelFormat.UNKNOWN; + } + + public void setRegion(Rect rect) { + int width = Util.getScreenWidth(mContext); + int height = Util.getScreenHeight(mContext); + rect.set((width - mCropWidth) / 2, (height - mCropHeight) / 2, (width + mCropWidth) / 2, (height + mCropHeight) / 2); + } + + public int getLeft() { + return mLeft; + } + + + public int getRight() { + return mRight; + } + + + public int getTop() { + return mCropHeight; + } + + + public int getBottom() { + return mBottom; + } + + public void setCropWidth(int mCropWidth) { + this.mCropWidth = mCropWidth; + } + + public void setCropHeight(int mCropHeight) { + this.mCropHeight = mCropHeight; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/crop/CropFloatView.java b/app/src/main/java/net/oschina/app/improve/media/crop/CropFloatView.java new file mode 100644 index 0000000000000000000000000000000000000000..b1e700c4673a948a98247745e51d5b0b2cb4c203 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/crop/CropFloatView.java @@ -0,0 +1,65 @@ +package net.oschina.app.improve.media.crop; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.Region; +import android.view.View; + +/** + * Created by haibin + * on 2016/12/2. + */ + +public class CropFloatView extends View { + private int mCropWidth;//设置裁剪宽度 + private int mCropHeight;//设置裁剪高度 + + private int mHOffset;//水平偏移量 + private int mVOffset;//垂直偏移量 + + private CropDrawable mCropDrawable; + private Rect mFloatRect = new Rect(); + private boolean isCrop; + + public CropFloatView(Context context) { + super(context); + mCropDrawable = new CropDrawable(context); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + cropDrawable(); + canvas.save(); + canvas.clipRect(mFloatRect, Region.Op.DIFFERENCE); + canvas.drawColor(Color.parseColor("#a0000000")); + canvas.restore(); + mCropDrawable.draw(canvas); + } + + private void cropDrawable() { + if (isCrop) return; + mCropDrawable.setRegion(mFloatRect); + isCrop = true; + } + + public void setCropWidth(int mCropWidth) { + this.mCropWidth = mCropWidth; + mCropDrawable.setCropWidth(mCropWidth); + } + + public void setCropHeight(int mCropHeight) { + this.mCropHeight = mCropHeight; + mCropDrawable.setCropHeight(mCropHeight); + } + + public void setHOffset(int mHOffset) { + this.mHOffset = mHOffset; + } + + public void setVOffset(int mVOffset) { + this.mVOffset = mVOffset; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/crop/CropLayout.java b/app/src/main/java/net/oschina/app/improve/media/crop/CropLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..f75ccdde52a7418646e741d652dbf49bdf41b640 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/crop/CropLayout.java @@ -0,0 +1,65 @@ +package net.oschina.app.improve.media.crop; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import net.oschina.app.improve.media.Util; + +/** + * Created by haibin + * on 2016/12/2. + */ +@SuppressWarnings("unused") +public class CropLayout extends FrameLayout { + private int mCropWidth = 500;//设置裁剪宽度 + private int mCropHeight = 500;//设置裁剪高度 + + private ZoomImageView mZoomImageView; + private CropFloatView mCropView; + + public CropLayout(Context context, AttributeSet attrs) { + super(context, attrs); + mZoomImageView = new ZoomImageView(context); + mCropView = new CropFloatView(context); + ViewGroup.LayoutParams lp = new LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + this.addView(mZoomImageView, lp); + this.addView(mCropView, lp); + } + + public ZoomImageView getImageView() { + return mZoomImageView; + } + + + public Bitmap cropBitmap() { + return mZoomImageView.cropBitmap(); + } + + public void setCropWidth(int mCropWidth) { + this.mCropWidth = mCropWidth; + mCropView.setCropWidth(mCropWidth); + mZoomImageView.setCropWidth(mCropWidth); + } + + public void setCropHeight(int mCropHeight) { + this.mCropHeight = mCropHeight; + mCropView.setCropHeight(mCropHeight); + mZoomImageView.setCropHeight(mCropHeight); + } + + public void start() { + int height = Util.getScreenHeight(getContext()); + int width = Util.getScreenWidth(getContext()); + int mHOffset = (width - mCropWidth) / 2; + int mVOffset = (height - mCropHeight) / 2; + mZoomImageView.setHOffset(mHOffset); + mZoomImageView.setVOffset(mVOffset); + mCropView.setHOffset(mHOffset); + mCropView.setVOffset(mVOffset); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/media/crop/ZoomImageView.java b/app/src/main/java/net/oschina/app/improve/media/crop/ZoomImageView.java new file mode 100644 index 0000000000000000000000000000000000000000..d4ef921bd1dbdb7704d20b5ed6dcbdaae36331e0 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/media/crop/ZoomImageView.java @@ -0,0 +1,374 @@ +package net.oschina.app.improve.media.crop; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.ScaleGestureDetector.OnScaleGestureListener; +import android.view.View; +import android.view.View.OnTouchListener; +import android.view.ViewTreeObserver; +import android.widget.ImageView; + +/** + * Created by haibin + * on 2016/12/2. + */ +public class ZoomImageView extends ImageView implements + OnScaleGestureListener, OnTouchListener, + ViewTreeObserver.OnGlobalLayoutListener { + + private int mCropWidth;//设置裁剪宽度 + private int mCropHeight;//设置裁剪高度 + + private int mOffset = 0; + private int mVOffset = 0; + + private float SCALE_MAX = 4.0f; + private float SCALE_MID = 2.0f; + private float SCALE_MIN = 1.0f; + + private float mScale = 1.0f; + private boolean isFirst = true; + + private final float[] mMatrixValues = new float[9]; + + private ScaleGestureDetector mScaleGestureDetector = null; + private Matrix mScaleMatrix = new Matrix(); + + private GestureDetector mGestureDetector; + private boolean isAutoScale; + + private boolean isInit; + + private float mLastX; + private float mLastY; + + private boolean isCanDrag; + private int lastPointerCount; + + public ZoomImageView(Context context) { + this(context, null); + } + + public ZoomImageView(Context context, AttributeSet attrs) { + super(context, attrs); + setScaleType(ScaleType.MATRIX); + mGestureDetector = new GestureDetector(context, + new SimpleOnGestureListener() { + @Override + public boolean onDoubleTap(MotionEvent e) { + if (isAutoScale) + return true; + float x = e.getX(); + float y = e.getY(); + if (getScale() < SCALE_MID) { + postDelayed(new ScaleRunnable(SCALE_MID, x, y), 16); + isAutoScale = true; + } else { + postDelayed(new ScaleRunnable(mScale, x, y), 16); + isAutoScale = true; + } + return true; + } + }); + mScaleGestureDetector = new ScaleGestureDetector(context, this); + this.setOnTouchListener(this); + } + + private class ScaleRunnable implements Runnable { + static final float BIGGER = 1.07f; + static final float SMALLER = 0.93f; + private float mTargetScale; + private float mScale; + private float x; + private float y; + + ScaleRunnable(float targetScale, float x, float y) { + this.mTargetScale = targetScale; + this.x = x; + this.y = y; + if (getScale() < mTargetScale) { + mScale = BIGGER; + } else { + mScale = SMALLER; + } + + } + + @Override + public void run() { + mScaleMatrix.postScale(mScale, mScale, x, y); + checkBorder(); + setImageMatrix(mScaleMatrix); + + final float currentScale = getScale(); + if (((mScale > 1f) && (currentScale < mTargetScale)) || ((mScale < 1f) && (mTargetScale < currentScale))) { + postDelayed(this, 16); + } else { + final float deltaScale = mTargetScale / currentScale; + mScaleMatrix.postScale(deltaScale, deltaScale, x, y); + checkBorder(); + setImageMatrix(mScaleMatrix); + isAutoScale = false; + } + } + } + + @Override + public boolean onScale(ScaleGestureDetector detector) { + float scale = getScale(); + float scaleFactor = detector.getScaleFactor(); + + if (getDrawable() == null) + return true; + if ((scale < SCALE_MAX && scaleFactor > SCALE_MIN) + || (scale > mScale && scaleFactor < SCALE_MIN)) { + if (scaleFactor * scale < SCALE_MIN) { + scaleFactor = SCALE_MIN / scale; + } + if (scaleFactor * scale > SCALE_MAX) { + scaleFactor = SCALE_MAX / scale; + } + + mScaleMatrix.postScale(scaleFactor, scaleFactor, + detector.getFocusX(), detector.getFocusY()); + checkBorder(); + setImageMatrix(mScaleMatrix); + } + return true; + + } + + private RectF getMatrixRectF() { + Matrix matrix = mScaleMatrix; + RectF rect = new RectF(); + Drawable d = getDrawable(); + if (null != d) { + rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); + matrix.mapRect(rect); + } + return rect; + } + + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + return true; + } + + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + + if (mGestureDetector.onTouchEvent(event)) + return true; + mScaleGestureDetector.onTouchEvent(event); + + float x = 0, y = 0; + final int pointerCount = event.getPointerCount(); + for (int i = 0; i < pointerCount; i++) { + x += event.getX(i); + y += event.getY(i); + } + x = x / pointerCount; + y = y / pointerCount; + + if (pointerCount != lastPointerCount) { + isCanDrag = false; + mLastX = x; + mLastY = y; + } + + lastPointerCount = pointerCount; + switch (event.getAction()) { + case MotionEvent.ACTION_MOVE: + float dx = x - mLastX; + float dy = y - mLastY; + + if (!isCanDrag) { + isCanDrag = isCanDrag(dx, dy); + } + if (isCanDrag) { + if (getDrawable() != null) { + + RectF rectF = getMatrixRectF(); + if (rectF.width() <= getWidth() - mOffset * 2) { + dx = 0; + } + if (rectF.height() <= getHeight() - mVOffset * 2) { + dy = 0; + } + mScaleMatrix.postTranslate(dx, dy); + checkBorder(); + setImageMatrix(mScaleMatrix); + } + } + mLastX = x; + mLastY = y; + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + lastPointerCount = 0; + break; + } + + return true; + } + + public final float getScale() { + mScaleMatrix.getValues(mMatrixValues); + return mMatrixValues[Matrix.MSCALE_X]; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + getViewTreeObserver().addOnGlobalLayoutListener(this); + } + + @SuppressWarnings("deprecation") + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mScaleMatrix = null; + getViewTreeObserver().removeGlobalOnLayoutListener(this); + } + + @Override + protected boolean setFrame(int l, int t, int r, int b) { + if (isInit) return false; + boolean change = super.setFrame(l, t, r, b); + Drawable drawable = getDrawable(); + if (drawable == null) return false; + int boundWidth = drawable.getBounds().width(); + int boundHeight = drawable.getBounds().height(); + if (boundWidth > mCropWidth || boundHeight > mCropHeight) return false; + int width = getWidth(); + int height = getHeight(); + mScale = (float) width / boundWidth; + isInit = true; + postDelayed(new ScaleRunnable(mScale, width / 2, height / 2), 50); + isAutoScale = false; + return change; + } + + + @Override + public void onGlobalLayout() { + if (isFirst) { + Drawable d = getDrawable(); + if (d == null) + return; + mVOffset = (getHeight() - (getWidth() - 2 * mOffset)) / 2; + + int width = getWidth(); + int height = getHeight(); + + int dw = d.getIntrinsicWidth(); + int dh = d.getIntrinsicHeight(); + float scale = 1.0f; + if (dw < getWidth() - mOffset * 2 + && dh > getHeight() - mVOffset * 2) { + scale = (getWidth() * 1.0f - mOffset * 2) / dw; + } + + if (dh < getHeight() - mVOffset * 2 + && dw > getWidth() - mOffset * 2) { + scale = (getHeight() * 1.0f - mVOffset * 2) / dh; + } + + if (dw < getWidth() - mOffset * 2 + && dh < getHeight() - mVOffset * 2) { + float scaleW = (getWidth() * 1.0f - mOffset * 2) + / dw; + float scaleH = (getHeight() * 1.0f - mVOffset * 2) / dh; + scale = Math.max(scaleW, scaleH); + } + + float sw = (float) (width - 2 * mOffset) / (float) dw; + float sh = (float) (height - 2 * mVOffset) / (float) dh; + + SCALE_MIN = Math.max(sw, sh); + if (SCALE_MIN >= 1.0F) SCALE_MIN = 1.0F; + + mScale = scale; + SCALE_MID = mScale * 2; + SCALE_MAX = mScale * 4; + mScaleMatrix.postTranslate((width - dw) / 2, (height - dh) / 2); + mScaleMatrix.postScale(scale, scale, getWidth() / 2, + getHeight() / 2); + setImageMatrix(mScaleMatrix); + isFirst = false; + } + + } + + public Bitmap cropBitmap() { + Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + draw(canvas); + return Bitmap.createBitmap(bitmap, mOffset, + mVOffset, getWidth() - 2 * mOffset, + getWidth() - 2 * mOffset); + } + + private void checkBorder() { + RectF rect = getMatrixRectF(); + float deltaX = 0; + float deltaY = 0; + + int width = getWidth(); + int height = getHeight(); + + if (rect.width() + 0.01 >= width - 2 * mOffset) { + if (rect.left > mOffset) { + deltaX = -rect.left + mOffset; + } + if (rect.right < width - mOffset) { + deltaX = width - mOffset - rect.right; + } + } + if (rect.height() + 0.01 >= height - 2 * mVOffset) { + if (rect.top > mVOffset) { + deltaY = -rect.top + mVOffset; + } + if (rect.bottom < height - mVOffset) { + deltaY = height - mVOffset - rect.bottom; + } + } + mScaleMatrix.postTranslate(deltaX, deltaY); + + } + + private boolean isCanDrag(float dx, float dy) { + return Math.sqrt((dx * dx) + (dy * dy)) >= 0; + } + + public void setHOffset(int hOffset) { + this.mOffset = hOffset; + } + + public void setVOffset(int vOffset) { + this.mVOffset = vOffset; + } + + public void setCropWidth(int mCropWidth) { + this.mCropWidth = mCropWidth; + } + + public void setCropHeight(int mCropHeight) { + this.mCropHeight = mCropHeight; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/nearby/BDRadarManager.java b/app/src/main/java/net/oschina/app/improve/nearby/BDRadarManager.java new file mode 100644 index 0000000000000000000000000000000000000000..97af8a739459b33e935f594e5efb426c25cc1046 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/nearby/BDRadarManager.java @@ -0,0 +1,69 @@ +package net.oschina.app.improve.nearby; + +import com.baidu.mapapi.model.LatLng; +import com.baidu.mapapi.radar.RadarNearbySearchOption; +import com.baidu.mapapi.radar.RadarNearbySearchSortType; +import com.baidu.mapapi.radar.RadarSearchListener; +import com.baidu.mapapi.radar.RadarSearchManager; +import com.baidu.mapapi.radar.RadarUploadInfo; + +import net.oschina.app.OSCApplication; +import net.oschina.app.Setting; + +/** + * 百度雷达管理器 + * Created by huanghaibin on 2018/3/15. + */ +@SuppressWarnings("unused") +final class BDRadarManager { + private RadarSearchManager mRadarManager; + private RadarSearchListener mListener; + + BDRadarManager(RadarSearchListener listener) { + mRadarManager = RadarSearchManager.getInstance(); + mListener = listener; + mRadarManager.addNearbyInfoListener(listener); + } + + /** + * 查询附近的人 + * + * @param latLng 用户定位信息 + * @param pageIndex 页码 + */ + void requestNearby(LatLng latLng, int pageIndex) { + if (mRadarManager == null) + return; + //构造请求参数,其中centerPt是自己的位置坐标 + RadarNearbySearchOption option = new RadarNearbySearchOption() + .centerPt(latLng).pageNum(pageIndex).radius(38000).pageCapacity(50). + sortType(RadarNearbySearchSortType.distance_from_far_to_near); + //发起查询请求 + mRadarManager.nearbyInfoRequest(option); + } + + void setUserId(String userId) { + mRadarManager.setUserID(userId); + } + + void uploadInfoRequest(RadarUploadInfo info) { + mRadarManager.uploadInfoRequest(info); + } + + /** + * 关闭释放资源 + */ + void release() { + if (mRadarManager != null && mListener != null) { + mRadarManager.removeNearbyInfoListener(mListener); + mRadarManager.destroy(); + mRadarManager = null; + } + } + + void clear() { + if (mRadarManager == null) return; + mRadarManager.clearUserInfo(); + Setting.updateLocationInfo(OSCApplication.getInstance(), false); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/nearby/BaiDuLocation.java b/app/src/main/java/net/oschina/app/improve/nearby/BaiDuLocation.java new file mode 100644 index 0000000000000000000000000000000000000000..12a1793f491c3d9f29fed27951310208939e0754 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/nearby/BaiDuLocation.java @@ -0,0 +1,55 @@ +package net.oschina.app.improve.nearby; + +import android.content.Context; + +import com.baidu.location.BDLocationListener; +import com.baidu.location.LocationClient; +import com.baidu.location.LocationClientOption; + +/** + * 百度定位 + * Created by huanghaibin on 2018/3/15. + */ +@SuppressWarnings("unused") +final class BaiDuLocation { + private LocationClientOption.LocationMode tempMode = LocationClientOption.LocationMode.Hight_Accuracy; + private static final String coorType = "bd09ll"; + //coorType - 取值有3个: 返回国测局经纬度坐标系:gcj02 返回百度墨卡托坐标系 :bd09 返回百度经纬度坐标系 :bd09ll + private static final int scanSpan = 3600000 * 3;//定位间隔 + + private LocationClient mLocationClient; + + BaiDuLocation(Context context, BDLocationListener listener) { + mLocationClient = new LocationClient(context.getApplicationContext()); + initConfig(); + //mLocationClient.registerLocationListener(listener); + mLocationClient.registerNotifyLocationListener(listener); + } + + void start() { + if (!isStart()) + mLocationClient.start(); + } + + boolean isStart() { + return mLocationClient.isStarted(); + } + + void stop() { + if (isStart()) + mLocationClient.stop(); + } + + void release() { + mLocationClient.stop(); + } + + private void initConfig() { + LocationClientOption option = new LocationClientOption(); + option.setLocationMode(tempMode);//设置定位模式 + option.setCoorType(coorType);//返回的定位结果是百度经纬度,默认值bd09ll 设置返回值的坐标类型 + option.setScanSpan(scanSpan);//定位间隔,默认3小时 + option.setIsNeedAddress(true); //是否需要地址 + mLocationClient.setLocOption(option); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/nearby/NearbyActivity.java b/app/src/main/java/net/oschina/app/improve/nearby/NearbyActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..7f38b579b8ec85983d08b68c4189bf00482fbca7 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/nearby/NearbyActivity.java @@ -0,0 +1,346 @@ +package net.oschina.app.improve.nearby; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import com.baidu.location.BDLocation; +import com.baidu.location.BDLocationListener; +import com.baidu.mapapi.radar.RadarNearbyResult; +import com.baidu.mapapi.radar.RadarSearchError; +import com.baidu.mapapi.radar.RadarSearchListener; + +import net.oschina.app.R; +import net.oschina.app.Setting; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.NearbyResult; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.BottomDialog; +import net.oschina.app.improve.widget.RecyclerRefreshLayout; +import net.oschina.app.improve.widget.SimplexToast; + +import java.util.List; + +import butterknife.Bind; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * 寻找附近的程序员 + * Created by huanghaibin on 2018/3/15. + */ + +public class NearbyActivity extends BackActivity implements + View.OnClickListener, + RadarSearchListener, + BDLocationListener, + NearbyContract.View, + EasyPermissions.PermissionCallbacks, + RecyclerRefreshLayout.SuperRefreshLayoutListener, + BaseRecyclerAdapter.OnItemClickListener { + + private static final int LC = 0x3; + + @Bind(R.id.recyclerView) + RecyclerView mRecyclerView; + @Bind(R.id.refreshLayout) + RecyclerRefreshLayout mRefreshLayout; + + private BottomDialog mSelectorDialog; + + private NearbyAdapter mAdapter; + + private NearbyPresenter mPresenter; + + public static void show(Context context) { + if (!AccountHelper.isLogin()) { + LoginActivity.show(context); + return; + } + Intent intent = new Intent(context, NearbyActivity.class); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_nearby_v2; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + mAdapter = new NearbyAdapter(this); + mRecyclerView.setAdapter(mAdapter); + mRefreshLayout.setSuperRefreshLayoutListener(this); + mAdapter.setOnItemClickListener(this); + mRefreshLayout.post(new Runnable() { + @Override + public void run() { + mRefreshLayout.setRefreshing(true); + } + }); + } + + @Override + protected void initData() { + super.initData(); + mPresenter = new NearbyPresenter(this, AccountHelper.getUser()); + mPresenter.mLocationManager = new BaiDuLocation(this, this); + mPresenter.mRadarManager = new BDRadarManager(this); + tryLocation(); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.tv_clear_opt: + mPresenter.mRadarManager.clear(); + if (mSelectorDialog.isShowing()) + mSelectorDialog.cancel(); + break; + case R.id.tv_cancel_opt: + if (mSelectorDialog.isShowing()) + mSelectorDialog.cancel(); + break; + } + } + + @Override + public void onItemClick(int position, long itemId) { + NearbyResult result = mAdapter.getItem(position); + if (result == null) return; + OtherUserHomeActivity.show(this, result.getUser()); + } + + @Override + public void onRefreshing() { + if (mPresenter == null) + return; + mPresenter.onRefreshing(); + } + + @Override + public void onLoadMore() { + if (mPresenter == null) + return; + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + mPresenter.onLoadMore(); + } + + @Override + public void onScrollToBottom() { + // TODO: 2018/3/15 + } + + /** + * 定位 + */ + @SuppressLint("ObsoleteSdkInt") + @AfterPermissionGranted(LC) + private void tryLocation() { + String[] perms; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { + perms = new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.READ_PHONE_STATE}; + } else { + perms = new String[]{Manifest.permission.ACCESS_FINE_LOCATION}; + } + if (EasyPermissions.hasPermissions(this, perms)) { + mPresenter.mLocationManager.start(); + } else { + EasyPermissions.requestPermissions(this, "使用地理位置需要获取权限,请提供权限", + LC, perms); + } + } + + /** + * 接收了定位信息,处理 + * + * @param bdLocation bdLocation + */ + @Override + public void onReceiveLocation(BDLocation bdLocation) { + mPresenter.onReceiveLocation(this, bdLocation); + } + + /** + * 雷达定位结果 + * + * @param radarNearbyResult radarNearbyResult + * @param radarSearchError radarSearchError + */ + @Override + public void onGetNearbyInfoList(RadarNearbyResult radarNearbyResult, RadarSearchError radarSearchError) { + mPresenter.updateNearbyInfoList(radarNearbyResult); + } + + /** + * 雷达上传结果 + * + * @param radarSearchError radarSearchError + */ + @Override + public void onGetUploadState(RadarSearchError radarSearchError) { + mPresenter.onGetUploadState(radarSearchError); + } + + /** + * 清除雷达定位 + * + * @param radarSearchError radarSearchError + */ + @Override + public void onGetClearInfoState(RadarSearchError radarSearchError) { + if (isDestroy()) + return; + switch (radarSearchError) { + case RADAR_NO_RESULT://未上传有雷达信息 + Setting.updateLocationInfo(getApplicationContext(), false); + break; + case RADAR_NO_ERROR://清除雷达信息成功 + Setting.updateLocationInfo(getApplicationContext(), false); + supportFinishAfterTransition(); + break; + default: + SimplexToast.show(this, getString(R.string.clear_bodies_failed_hint)); + break; + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_nearby_more, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_item_more: + getSelectorDialog().show(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private Dialog getSelectorDialog() { + if (mSelectorDialog == null) { + mSelectorDialog = new BottomDialog(this, true); + @SuppressLint("InflateParams") View view = LayoutInflater.from(this).inflate(R.layout.view_nearby_operator, null, false); + view.findViewById(R.id.tv_clear_opt).setOnClickListener(this); + view.findViewById(R.id.tv_cancel_opt).setOnClickListener(this); + mSelectorDialog.setContentView(view); + ViewGroup parent = (ViewGroup) view.getParent(); + if (parent != null) { + parent.setBackgroundResource(R.color.transparent); + } + } + return mSelectorDialog; + } + + @Override + public void showLocationError(int strId) { + if (isDestroy()) + return; + SimplexToast.show(this, strId); + } + + @Override + public void showUploadError(int strId) { + if (isDestroy()) + return; + SimplexToast.show(this, strId); + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + DialogHelper.getConfirmDialog(this, + "去设置权限", + "使用地理位置需要获取权限, 你需要去[设置-应用-开源中国]中设置权限.否则无法使用附近程序员功能", + "去设置", "取消", false, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + + @Override + public void showNetworkError(int strId) { + + } + + @Override + public void onRefreshSuccess(List data) { + if (isDestroy()) + return; + mAdapter.resetItem(data); + } + + @Override + public void onLoadMoreSuccess(List data) { + if (isDestroy()) + return; + mAdapter.addAll(data); + } + + @Override + public void showNotMore() { + if (isDestroy()) + return; + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + } + + @Override + public void onComplete() { + mRefreshLayout.onComplete(); + } + + @Override + public void setPresenter(NearbyContract.Presenter presenter) { + + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mPresenter.onRelease(); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/nearby/NearbyAdapter.java b/app/src/main/java/net/oschina/app/improve/nearby/NearbyAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..997b682d719e72972163cc8e890b5be2cba9f036 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/nearby/NearbyAdapter.java @@ -0,0 +1,96 @@ +package net.oschina.app.improve.nearby; + +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 net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.NearbyResult; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * 附近的程序员 + * Created by huanghaibin on 2018/3/15. + */ + +class NearbyAdapter extends BaseRecyclerAdapter { + NearbyAdapter(Context context) { + super(context, ONLY_FOOTER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_nearby_user, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder h, NearbyResult item, int position) { + if (h instanceof ViewHolder) { + ViewHolder holder = (ViewHolder) h; + holder.mIdentityView.setup(item.getUser()); + if (item.getUser() != null) { + holder.mViewPortrait.setup(item.getUser()); + holder.mViewNick.setText(item.getUser().getName()); + holder.mViewGender.setVisibility(View.VISIBLE); + switch (item.getUser().getGender()) { + case User.GENDER_FEMALE: + holder.mViewGender.setImageResource(R.mipmap.ic_female); + break; + case User.GENDER_MALE: + holder.mViewGender.setImageResource(R.mipmap.ic_male); + break; + default: + holder.mViewGender.setVisibility(View.GONE); + } + User.More more = item.getUser().getMore(); + if (more != null) { + holder.mViewPosition.setText(more.getCompany()); + } else { + holder.mViewPosition.setText("??? ???"); + } + } else { + holder.mViewPortrait.setup(0, "?", ""); + holder.mViewNick.setText("???"); + holder.mViewGender.setVisibility(View.GONE); + holder.mViewPosition.setText("??? ???"); + } + + if (item.getNearby() != null) { + holder.mViewDistance.setText(StringUtils.formatDistance(item.getNearby().getDistance())); + } else { + holder.mViewDistance.setText("未知距离"); + } + } + } + + static class ViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.identityView) + IdentityView mIdentityView; + @Bind(R.id.iv_portrait) + PortraitView mViewPortrait; + @Bind(R.id.tv_nick) + TextView mViewNick; + @Bind(R.id.tv_position) + TextView mViewPosition; + @Bind(R.id.tv_distance) + TextView mViewDistance; + @Bind(R.id.iv_gender) + ImageView mViewGender; + + public ViewHolder(View view) { + super(view); + ButterKnife.bind(this, view); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/nearby/NearbyContract.java b/app/src/main/java/net/oschina/app/improve/nearby/NearbyContract.java new file mode 100644 index 0000000000000000000000000000000000000000..26a0762b7a0eaa94013e29acadc550abcb5aabfa --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/nearby/NearbyContract.java @@ -0,0 +1,35 @@ +package net.oschina.app.improve.nearby; + +import android.content.Context; + +import com.baidu.location.BDLocation; +import com.baidu.mapapi.radar.RadarNearbyResult; +import com.baidu.mapapi.radar.RadarSearchError; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.bean.NearbyResult; + +/** + * 附近的程序员 + * Created by huanghaibin on 2018/3/15. + */ + +interface NearbyContract { + + interface View extends BaseListView { + void showLocationError(int strId); + + void showUploadError(int strId); + } + + interface Presenter extends BaseListPresenter { + void onReceiveLocation(Context context, BDLocation location); + + void updateNearbyInfoList(RadarNearbyResult result); + + void onGetUploadState(RadarSearchError error); + + void onRelease(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/nearby/NearbyPresenter.java b/app/src/main/java/net/oschina/app/improve/nearby/NearbyPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..8dbae135e75e6812ee6520b8b1ea00fe9cd93e64 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/nearby/NearbyPresenter.java @@ -0,0 +1,302 @@ +package net.oschina.app.improve.nearby; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; + +import com.baidu.location.BDLocation; +import com.baidu.mapapi.model.LatLng; +import com.baidu.mapapi.radar.RadarNearbyInfo; +import com.baidu.mapapi.radar.RadarNearbyResult; +import com.baidu.mapapi.radar.RadarSearchError; +import com.baidu.mapapi.radar.RadarUploadInfo; + +import net.oschina.app.AppContext; +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.Setting; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.NearbyResult; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.util.TDevice; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +/** + * 寻找附近的程序员 + * Created by huanghaibin on 2018/3/15. + */ + +class NearbyPresenter implements NearbyContract.Presenter { + private final NearbyContract.View mView; + private final User mUser; + private static final String CHARSET = "UTF-8"; + private boolean mIsFirstLocation = true; + BaiDuLocation mLocationManager; + BDRadarManager mRadarManager; + + private int mNextPageIndex = 0; + + private LatLng mUserLatLng; + + NearbyPresenter(NearbyContract.View mView, User mUser) { + this.mView = mView; + this.mUser = mUser; + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + + if (mUserLatLng == null || (mUserLatLng.latitude == 4.9E-324 && mUserLatLng.longitude == 4.9E-324)) { + mLocationManager.start(); + mView.onComplete(); + return; + } + mRadarManager.requestNearby(mUserLatLng, 0); + mIsFirstLocation = false; + } + + @Override + public void onLoadMore() { + if (mUserLatLng == null || (mUserLatLng.latitude == 4.9E-324 && mUserLatLng.longitude == 4.9E-324)) { + mLocationManager.start(); + mView.onComplete(); + return; + } + mRadarManager.requestNearby(mUserLatLng, mNextPageIndex); + mIsFirstLocation = false; + } + + + @Override + public void updateNearbyInfoList(RadarNearbyResult result) { + if (result == null) { + mView.showNotMore(); + mView.onComplete(); + return; + } + Log.e("updateNearbyInfoList", " --- "); + //pageNum==0,表示初始化数据,有可能是刷新,也有可能是第一次加载 + List infoList = result.infoList; + if (infoList == null || infoList.size() == 0) { + mView.showNotMore(); + return; + } + List items = new ArrayList<>(); + int pageIndex = result.pageIndex; + if (pageIndex == 0) {//第一次,第一页 + + Log.e("updateNearbyInfoList", " --- 0 ---- "); + //没有缓存数据,直接添加 + for (RadarNearbyInfo info : infoList) { + User user = null; + try { + String comments = URLDecoder.decode(info.comments, CHARSET); + user = AppOperator.createGson().fromJson(comments, User.class); + } catch (Exception e) { + e.printStackTrace(); + } + + if (user == null || (user.getId() == 0 && TextUtils.isEmpty(user.getName()))) + continue; + + NearbyResult.Nearby nearby = new NearbyResult.Nearby(); + nearby.setDistance(info.distance); + nearby.setMobileName(info.mobileName); + nearby.setMobileOS(info.mobileOS); + items.add(new NearbyResult(user, nearby)); + } + mNextPageIndex = 1; + mView.onRefreshSuccess(items); + } else { + Log.e("updateNearbyInfoList", " --- index ---- " + pageIndex); + for (RadarNearbyInfo info : infoList) { + User user = null; + try { + String comments = URLDecoder.decode(info.comments, CHARSET); + user = AppOperator.createGson().fromJson(comments, User.class); + } catch (Exception e) { + e.printStackTrace(); + } + + if (user == null || (user.getId() == 0 && TextUtils.isEmpty(user.getName()))) + continue; + + int index = containsFriend(user, items); + + if (index == -1) { + NearbyResult.Nearby nearby = new NearbyResult.Nearby(); + nearby.setDistance(info.distance); + nearby.setMobileName(info.mobileName); + nearby.setMobileOS(info.mobileOS); + items.add(new NearbyResult(user, nearby)); + } + } + mNextPageIndex += 1; + mView.onLoadMoreSuccess(items); + } + mView.onComplete(); + } + + /** + * check is cache + * + * @param user load_user + * @return isCache?index:-1 + */ + private int containsFriend(User user, List items) { + int index = -1; + for (int i = 0; i < items.size(); i++) { + if (items.get(i).getUser().getId() == user.getId()) { + index = i; + break; + } + } + return index; + } + + @Override + public void onReceiveLocation(Context context, BDLocation location) { + final int code = location.getLocType(); + switch (code) { + case BDLocation.TypeCriteriaException://62 + mLocationManager.start(); + mView.showLocationError(R.string.no_location_hint); + mView.onComplete(); + return; + case BDLocation.TypeNetWorkException://63 + mView.showLocationError(R.string.network_exception_hint); + mView.onComplete(); + return; + case BDLocation.TypeServerError://167 + mView.showLocationError(R.string.server_no_have_permission_hint); + mView.onComplete(); + return; + case BDLocation.TypeNetWorkLocation://161 + //mView.showLocationError(R.string.tip_network_error); + break; + case BDLocation.TypeOffLineLocation://66 离线模式 + mView.showLocationError(R.string.tip_network_error); + break; + } + + if (code >= 501) { + mView.showLocationError(R.string.key_is_invalid_hint); + return; + } + + if (TDevice.hasInternet() && location.getLatitude() != 4.9E-324 && location.getLongitude() != 4.9E-324) { + + boolean started = mLocationManager.isStart(); + + if (started) { + mLocationManager.stop(); + } + + mUserLatLng = new LatLng(location.getLatitude(), location.getLongitude()); + + Setting.updateLocationPermission(context, true); + + //周边雷达设置用户身份标识,id为空默认是设备标识 + String userId; + + //上传位置 + RadarUploadInfo info = new RadarUploadInfo(); + + userId = String.valueOf(mUser.getId()); + + try { + SampleAuthor author = new SampleAuthor(mUser); + String authorJson = AppOperator.getGson().toJson(author); + info.comments = URLEncoder.encode(authorJson, CHARSET); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + mView.showLocationError(R.string.upload_lbs_info_hint); + return; + } + mRadarManager.setUserId(userId); + info.pt = mUserLatLng; + Log.e("定位成功", " -- " + mUserLatLng.toString()); + mRadarManager.uploadInfoRequest(info); + mRadarManager.requestNearby(mUserLatLng, 0); + } else { + mView.showLocationError(R.string.tip_network_error); + } + } + + + @Override + public void onGetUploadState(RadarSearchError error) { + switch (error) { + case RADAR_NETWORK_ERROR: + case RADAR_NETWORK_TIMEOUT: + if (mNextPageIndex == 0) { + mView.showUploadError(R.string.network_timeout_hint); + } else { + AppContext.showToastShort(R.string.request_error_hint); + } + mView.showUploadError(R.string.upload_lbs_info_hint); + break; + case RADAR_NO_ERROR: + if (mIsFirstLocation) { + Setting.updateLocationInfo(OSCApplication.getInstance(), true); + onRefreshing(); + } + break; + case RADAR_PERMISSION_UNFINISHED: + //ShowSettingDialog(); + break; + } + } + + @Override + public void onRelease() { + mRadarManager.release(); + mLocationManager.release(); + } + + /** + * 定位信息是否有效 + * + * @return 定位信息是否有效 + */ + private boolean isLatlngEnable() { + return mUserLatLng != null && (mUserLatLng.latitude == 4.9E-324 && mUserLatLng.longitude == 4.9E-324); + } + + private static class SampleAuthor { + public long id; + public String name; + public String portrait; + public int gender; + Author.Identity identity; + public SampleAuthorMore more; + + private SampleAuthor(User user) { + this.id = user.getId(); + this.name = user.getName(); + this.portrait = user.getPortrait(); + this.gender = user.getGender(); + this.identity = user.getIdentity(); + this.more = new SampleAuthorMore(user.getMore() != null ? user.getMore().getCompany() : ""); + if (this.identity == null) + this.identity = new Author.Identity(); + } + } + + + private static class SampleAuthorMore { + String company; + + private SampleAuthorMore(String company) { + company = company == null ? "" : company; + this.company = company; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/notice/NoticeBean.java b/app/src/main/java/net/oschina/app/improve/notice/NoticeBean.java new file mode 100644 index 0000000000000000000000000000000000000000..8fe588c2fa95ffa60af5bf6c0577cf1d15816576 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/notice/NoticeBean.java @@ -0,0 +1,147 @@ +package net.oschina.app.improve.notice; + +import android.content.Context; + +import net.oschina.common.helper.SharedPreferencesHelper; + +import java.io.Serializable; + +/** + * Created by JuQiu + * on 16/8/19. + * Note: like count always zero + */ +public class NoticeBean implements Serializable { + private int mention; + private int letter; + private int review; + private int fans; + private int like = 0; + private int newsCount; + + public int getNewsCount() { + return newsCount; + } + + public void setNewsCount(int newsCount) { + this.newsCount = newsCount; + } + + public int getMention() { + return mention; + } + + void setMention(int mention) { + this.mention = mention; + } + + public int getLetter() { + return letter; + } + + void setLetter(int letter) { + this.letter = letter; + } + + public int getReview() { + return review; + } + + void setReview(int review) { + this.review = review; + } + + public int getFans() { + return fans; + } + + void setFans(int fans) { + this.fans = fans; + } + + public int getLike() { + return like; + } + + void setLike(int like) { + this.like = 0; + } + + public int getAllCount() { + return mention + letter + review + fans; + } + + public int getUserCount() { + return mention + letter + review + fans; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + NoticeBean that = (NoticeBean) o; + + return mention == that.mention + && letter == that.letter + && review == that.review + && fans == that.fans + && like == that.like + && newsCount == that.newsCount; + + } + + @Override + public String toString() { + return "NoticeBean{" + + "mention=" + mention + + ", letter=" + letter + + ", review=" + review + + ", fans=" + fans + + ", like=" + like + + '}'; + } + + void clear() { + this.mention = 0; + this.letter = 0; + this.review = 0; + this.fans = 0; + this.like = 0; + this.newsCount = 0; + } + + NoticeBean set(NoticeBean bean) { + this.mention = bean.mention; + this.letter = bean.letter; + this.review = bean.review; + this.fans = bean.fans; + this.newsCount = bean.newsCount; + // 暂不累加点赞数据 + //this.like = bean.like; + return this; + } + + NoticeBean add(NoticeBean bean) { + this.mention += bean.mention; + this.letter += bean.letter; + this.review += bean.review; + this.fans += bean.fans; + this.newsCount += bean.newsCount; + // 暂不累加点赞数据 + //this.like += bean.like; + return this; + } + + NoticeBean save(Context context) { + SharedPreferencesHelper.save(context, this); + return this; + } + + static NoticeBean getInstance(Context context) { + NoticeBean bean = SharedPreferencesHelper.load(context, NoticeBean.class); + if (bean == null) + bean = new NoticeBean(); + return bean; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/notice/NoticeManager.java b/app/src/main/java/net/oschina/app/improve/notice/NoticeManager.java new file mode 100644 index 0000000000000000000000000000000000000000..4dbd6359be64c12eee4b59fc72481658e22eb7e2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/notice/NoticeManager.java @@ -0,0 +1,186 @@ +package net.oschina.app.improve.notice; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import net.oschina.app.OSCApplication; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.util.TLog; +import net.oschina.common.BuildConfig; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by JuQiu + * on 16/8/18. + */ +public final class NoticeManager { + public static final int FLAG_CLEAR_MENTION = 0x00000001; + public static final int FLAG_CLEAR_LETTER = 0x00000010; + public static final int FLAG_CLEAR_REVIEW = 0x00000100; + public static final int FLAG_CLEAR_FANS = 0x00001000; + public static final int FLAG_CLEAR_LIKE = 0x00010000; + public static final int FLAG_CLEAR_ALL = 0x00011111; + + private static NoticeManager INSTANCE; + + private NoticeManager() { + } + + private static synchronized NoticeManager instance() { + if (INSTANCE == null) + INSTANCE = new NoticeManager(); + return INSTANCE; + } + + private final List mNotifies = new ArrayList<>(); + private NoticeBean mNotice; + + + /** + * 服务初始化方法,可以启动服务 + * + * @param context Context + */ + public static void init(Context context) { + // 未登陆时不启动服务 + if (!AccountHelper.isLogin()) { + return; + } + // 启动服务 + NoticeServer.startAction(context); + // 注册广播 + IntentFilter filter = new IntentFilter(NoticeServer.FLAG_BROADCAST_REFRESH); + context.registerReceiver(instance().mReceiver, filter); + } + + public static void exitServer(Context context) { + NoticeServer.exitAction(context); + } + + public static void stopListen(Context context) { + try { + context.unregisterReceiver(instance().mReceiver); + } catch (IllegalArgumentException e) { + if (BuildConfig.DEBUG) + e.printStackTrace(); + } + } + + /** + * 发布一个新的消息,通过Result节点 + * + * @param resultBean ResultBean + * @param newNotice NoticeBean + */ + public static void publish(ResultBean resultBean, NoticeBean newNotice) { + if (resultBean != null && resultBean.isOk() && newNotice != null) { + TLog.d("NoticeManager", "publish:" + newNotice.toString()); + NoticeServer.arrivedMsgAction(OSCApplication.getInstance(), newNotice); + } + } + + + /** + * 直接得到当前的消息 + * + * @return NoticeBean + */ + public static NoticeBean getNotice() { + final NoticeBean bean = instance().mNotice; + if (bean == null) { + return new NoticeBean(); + } else { + return bean; + } + } + + /** + * 添加消息变化监听 + * + * @param noticeNotify NoticeNotify + */ + public static void bindNotify(NoticeNotify noticeNotify) { + instance().mNotifies.add(noticeNotify); + instance().check(noticeNotify); + } + + /*** + * 取消消息变化监听 + * @param noticeNotify NoticeNotify + */ + public static void unBindNotify(NoticeNotify noticeNotify) { + instance().mNotifies.remove(noticeNotify); + } + + /** + * 已读清理Context + * + * @param context Context + * @param type {@link #FLAG_CLEAR_MENTION}, {@link #FLAG_CLEAR_LETTER}, + * {@link #FLAG_CLEAR_REVIEW},{@link #FLAG_CLEAR_FANS}, + * {@link #FLAG_CLEAR_LIKE} + */ + public static void clear(Context context, int type) { + if (getNotice().getAllCount() > 0) + NoticeServer.clearAction(context, type); + } + + /** + * 绑定消息变化接口时进行一次检查,直接通知一次最新状态 + * + * @param noticeNotify NoticeNotify + */ + private void check(NoticeNotify noticeNotify) { + if (mNotice != null) + noticeNotify.onNoticeArrived(mNotice); + } + + /** + * 当消息改变时通知 + * + * @param bean NoticeBean + */ + private void onNoticeChanged(NoticeBean bean) { + mNotice = bean; + // Notify all + for (NoticeNotify notify : mNotifies) { + notify.onNoticeArrived(mNotice); + } + } + + /** + * 用于接收服务的消息 + */ + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction() != null) { + if (NoticeServer.FLAG_BROADCAST_REFRESH.equals(intent.getAction())) { + Serializable serializable = intent.getSerializableExtra(NoticeServer.EXTRA_BEAN); + if (serializable != null) { + try { + onNoticeChanged((NoticeBean) serializable); + } catch (Exception e) { + e.fillInStackTrace(); + } + } + } else if (NoticeServer.FLAG_BROADCAST_REQUEST.equals(intent.getAction())) { + // Do... + } + } + } + }; + + /** + * 消息变化时通知接口 + */ + public interface NoticeNotify { + void onNoticeArrived(NoticeBean bean); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/notice/NoticeServer.java b/app/src/main/java/net/oschina/app/improve/notice/NoticeServer.java new file mode 100644 index 0000000000000000000000000000000000000000..d5e466bf880443529f0b7f6cca127eadb2e79b64 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/notice/NoticeServer.java @@ -0,0 +1,350 @@ +package net.oschina.app.improve.notice; + +import android.annotation.SuppressLint; +import android.app.AlarmManager; +import android.app.Application; +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.main.MainActivity; +import net.oschina.app.improve.utils.ListenAccountChangeReceiver; +import net.oschina.app.util.TLog; + +import java.lang.reflect.Type; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by JuQiu + * on 16/8/18. + */ +public class NoticeServer extends Service { + private static final String TAG = NoticeServer.class.getName(); + + static final String FLAG_BROADCAST_REFRESH = TAG + "_BROADCAST_REFRESH"; + static final String FLAG_BROADCAST_REQUEST = TAG + "_BROADCAST_REQUEST"; + + private static final String FLAG_ACTION_FIRST = TAG + "_FIRST"; + private static final String FLAG_ACTION_REFRESH = TAG + "_REFRESH"; + private static final String FLAG_ACTION_CLEAR = TAG + "_CLEAR"; + private static final String FLAG_ACTION_EXIT = TAG + "_EXIT"; + private static final String FLAG_ACTION_ARRIVED = TAG + "_ARRIVED"; + + private static final String EXTRA_TYPE = "type"; + static final String EXTRA_BEAN = "bean"; + + private AlarmManager mAlarmMgr; + private ListenAccountChangeReceiver mListenAccountChangeReceiver; + + static void startAction(Context context) { + Intent intent = new Intent(context, NoticeServer.class); + intent.setAction(FLAG_ACTION_FIRST); + context.startService(intent); + log("startAction"); + } + + static void clearAction(Context context, int type) { + Intent intent = new Intent(context, NoticeServer.class); + intent.setAction(FLAG_ACTION_CLEAR); + intent.putExtra(EXTRA_TYPE, type); + context.startService(intent); + log("clearAction"); + } + + static void exitAction(Context context) { + Intent intent = new Intent(context, NoticeServer.class); + intent.setAction(FLAG_ACTION_EXIT); + context.startService(intent); + log("exitAction"); + } + + static void arrivedMsgAction(Context context, NoticeBean bean) { + int serviceUid = android.os.Process.getUidForName("net.oschina.app.notice.NoticeServer"); + int mUid = android.os.Process.myUid(); + + log("arrivedMsgAction: serviceUid:" + serviceUid + " mUid:" + mUid); + + if (mUid == serviceUid) + return; + + Intent intent = new Intent(context, NoticeServer.class); + intent.setAction(FLAG_ACTION_ARRIVED); + intent.putExtra(EXTRA_BEAN, bean); + context.startService(intent); + log("arrivedMsgAction"); + } + + public NoticeServer() { + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent == null) { + log("onStartCommand: intent is null."); + return super.onStartCommand(null, flags, startId); + } + String action = intent.getAction(); + log("onStartCommand:" + action); + if (action != null) { + handleAction(action, intent); + } + return super.onStartCommand(intent, flags, startId); + } + + @Override + public void onCreate() { + super.onCreate(); + log("onCreate"); + // First init the Client + Application application = getApplication(); + if (application instanceof OSCApplication) { + OSCApplication.reInit(); + } + + mAlarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE); + mListenAccountChangeReceiver = ListenAccountChangeReceiver.start(this); + } + + @Override + public void onDestroy() { + mListenAccountChangeReceiver.destroy(); + log("onDestroy"); + super.onDestroy(); + } + + private final static int ALARM_INTERVAL_SECOND = 100000; + + private void registerNextAlarm() { + cancelRequestAlarm(); + mAlarmMgr.setRepeating(AlarmManager.ELAPSED_REALTIME, + SystemClock.elapsedRealtime() + ALARM_INTERVAL_SECOND, ALARM_INTERVAL_SECOND, getOperationIntent()); + log("registerAlarmByInterval interval:" + ALARM_INTERVAL_SECOND); + } + + private void cancelRequestAlarm() { + mAlarmMgr.cancel(getOperationIntent()); + } + + private PendingIntent getOperationIntent() { + Intent intent = new Intent(this, NoticeServer.class); + intent.setAction(FLAG_ACTION_REFRESH); + return PendingIntent.getService(this, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } + + private void handleAction(String action, Intent intent) { + if (FLAG_ACTION_REFRESH.equals(action)) { + refreshNoticeForNet(); + } else if (FLAG_ACTION_CLEAR.equals(action)) { + clearNoticeForNet(intent.getIntExtra(EXTRA_TYPE, 0)); + } else if (FLAG_ACTION_EXIT.equals(action)) { + cancelRequestAlarm(); + stopSelf(); + } else if (FLAG_ACTION_FIRST.equals(action)) { + registerNextAlarm(); + // First notify + sendBroadcastToManager(NoticeBean.getInstance(this)); + } else if (FLAG_ACTION_ARRIVED.equals(action)) { + // Arrived new message + NoticeBean bean = (NoticeBean) intent.getSerializableExtra(EXTRA_BEAN); + if (bean == null) + return; + doNewMessage(bean); + } + } + + private void doNetFinish(ResultBean bean) { + log("doNetFinish:" + (bean == null ? "null" : bean.toString())); + if (bean != null && bean.isOk() + && bean.getNotice() != null) { + doNewMessage(bean.getNotice()); + } else { + // To register alarm + registerNextAlarm(); + } + } + + private void doNewMessage(@NonNull NoticeBean bean) { + log("doNewMessage:" + (bean.toString())); + NoticeBean notice = NoticeBean.getInstance(this); + if (bean.equals(notice)) + return; + notice.set(bean).save(this); + // Send to manager + sendBroadcastToManager(notice); + // Send to notification + sendNotification(notice); + // To register alarm + registerNextAlarm(); + } + + private void sendBroadcastToManager(NoticeBean bean) { + log("sendBroadcastToManager:" + bean.toString()); + Intent intent = new Intent(FLAG_BROADCAST_REFRESH); + intent.putExtra(EXTRA_BEAN, bean); + sendBroadcast(intent); + } + + private final static int NOTIFY_ID = 0x11111111; + + @SuppressLint("StringFormatMatches") + private void sendNotification(NoticeBean bean) { + if (bean == null || bean.getAllCount() == 0) { + clearNotification(); + return; + } + + log("sendNotification:" + bean.toString()); + + StringBuilder sb = new StringBuilder(); + if (bean.getMention() > 0) { + sb.append(getString(R.string.mention_count, bean.getMention())).append(" "); + } + if (bean.getLetter() > 0) { + sb.append(getString(R.string.letter_count, bean.getLetter())).append(" "); + } + if (bean.getReview() > 0) { + sb.append(getString(R.string.review_count, bean.getReview())) + .append(" "); + } + if (bean.getFans() > 0) { + sb.append(getString(R.string.fans_count, bean.getFans())); + } + if (bean.getLike() > 0) { + sb.append(getString(R.string.like_count, bean.getLike())); + } + String content = sb.toString(); + + Intent intent = new Intent(this, MainActivity.class); + intent.setAction(MainActivity.ACTION_NOTICE); + PendingIntent contentIntent = PendingIntent.getActivity(this, NOTIFY_ID, intent, + PendingIntent.FLAG_CANCEL_CURRENT); + + NotificationCompat.Builder builder = new NotificationCompat.Builder( + this) + .setTicker(content) + .setContentTitle(getString(R.string.you_have_unread_messages, bean.getAllCount())) + .setContentText(content) + .setAutoCancel(true) + .setOngoing(false) + .setContentIntent(contentIntent) + .setSmallIcon(R.mipmap.ic_notification); + + Notification notification = builder.build(); + NotificationManagerCompat.from(this).notify(NOTIFY_ID, notification); + } + + private void clearNotification() { + log("clearNotification"); + NotificationManagerCompat.from(this).cancel(NOTIFY_ID); + } + + + private boolean mRunning; + + private synchronized void refreshNoticeForNet() { + log("refreshNoticeForNet: mRunning:" + mRunning); + + mRunning = true; + OSChinaApi.getNotice( new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + doNetFinish(null); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (!TextUtils.isEmpty(responseString)) { + try { + Type type = new TypeToken() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + doNetFinish(bean); + } catch (Exception e) { + onFailure(statusCode, headers, responseString, e.fillInStackTrace()); + } + } else { + onFailure(statusCode, headers, responseString, null); + } + } + + @Override + public void onFinish() { + super.onFinish(); + mRunning = false; + } + }); + } + + private synchronized void clearNoticeForNet(int flag) { + log("clearNoticeForNet: flag:" + flag); + + if (flag == 0 || NoticeBean.getInstance(this).getAllCount() <= 0) + return; + + if ((flag & NoticeManager.FLAG_CLEAR_MENTION) == NoticeManager.FLAG_CLEAR_MENTION + || (flag & NoticeManager.FLAG_CLEAR_LETTER) == NoticeManager.FLAG_CLEAR_LETTER + || (flag & NoticeManager.FLAG_CLEAR_REVIEW) == NoticeManager.FLAG_CLEAR_REVIEW + || (flag & NoticeManager.FLAG_CLEAR_FANS) == NoticeManager.FLAG_CLEAR_FANS + || (flag & NoticeManager.FLAG_CLEAR_LIKE) == NoticeManager.FLAG_CLEAR_LIKE) { + mRunning = true; + OSChinaApi.clearNotice(flag, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + log("onFailure:" + statusCode + " " + responseString); + doNetFinish(null); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + log("onSuccess:" + statusCode + " " + responseString); + if (!TextUtils.isEmpty(responseString)) { + try { + Type type = new TypeToken() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + doNetFinish(bean); + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e.fillInStackTrace()); + } + } else { + onFailure(statusCode, headers, responseString, null); + } + } + + @Override + public void onFinish() { + super.onFinish(); + mRunning = false; + } + }); + } + } + + static void log(String str) { + TLog.d(TAG, str); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/search/activities/NearbyActivity.java b/app/src/main/java/net/oschina/app/improve/search/activities/NearbyActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..d309f96c82bf857da9f02e3953771edff178407e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/activities/NearbyActivity.java @@ -0,0 +1,833 @@ +package net.oschina.app.improve.search.activities; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.location.LocationManager; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; + +import com.baidu.location.BDLocation; +import com.baidu.location.BDLocationListener; +import com.baidu.location.LocationClient; +import com.baidu.location.LocationClientOption; +import com.baidu.mapapi.model.LatLng; +import com.baidu.mapapi.radar.RadarNearbyInfo; +import com.baidu.mapapi.radar.RadarNearbyResult; +import com.baidu.mapapi.radar.RadarNearbySearchOption; +import com.baidu.mapapi.radar.RadarNearbySearchSortType; +import com.baidu.mapapi.radar.RadarSearchError; +import com.baidu.mapapi.radar.RadarSearchListener; +import com.baidu.mapapi.radar.RadarSearchManager; +import com.baidu.mapapi.radar.RadarUploadInfo; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.Setting; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.NearbyResult; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.search.adapters.NearbyUserAdapter; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.BottomDialog; +import net.oschina.app.improve.widget.RecyclerRefreshLayout; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.util.TDevice; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.List; + +import butterknife.Bind; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * 寻找附近的人 + * Created by thanatosx + * on 2016/12/22. + * Updated bt fei + * on 2017/01/13 + */ + +public class NearbyActivity extends BackActivity implements RadarSearchListener, BDLocationListener, + RecyclerRefreshLayout.SuperRefreshLayoutListener, BaseRecyclerAdapter.OnItemClickListener, + EasyPermissions.PermissionCallbacks, View.OnClickListener { + + public static final int LOCATION_PERMISSION = 0x0100;//定位权限 + public static final String CHARSET = "UTF-8"; + + @Bind(R.id.recycler) + RecyclerView mRecycler; + + @Bind(R.id.layout_recycler_refresh) + RecyclerRefreshLayout mRecyclerRefresh; + + @Bind(R.id.lay_emptyLayout) + EmptyLayout mEmptyLayout; + private BaseRecyclerAdapter mAdapter; + + private int mNextPageIndex = 0; + private LatLng mUserLatLng; + + private LocationClient mLocationClient = null; + private RadarSearchManager mRadarSearchManager = null; + + private LocationManager mLocationManager; + + private boolean mIsFirstLocation = true; + private AlertDialog.Builder confirmDialog; + private AlertDialog alertDialog; + private BottomDialog mSelectorDialog; + + /** + * show activity + * + * @param context context + */ + public static void show(Context context) { + Intent intent = new Intent(context, NearbyActivity.class); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_nearby; + } + + @Override + protected void initWidget() { + setStatusBarDarkMode(); + setDarkToolBar(); + mEmptyLayout.setLoadingLocalFriend(true); + mEmptyLayout.setOnLayoutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + EmptyLayout emptyLayout = mEmptyLayout; + if (emptyLayout != null && emptyLayout.getErrorState() != EmptyLayout.HIDE_LAYOUT) { + onRefreshing(); + } + } + }); + + mRecycler.setLayoutManager(new LinearLayoutManager(this)); + mRecyclerRefresh.setSuperRefreshLayoutListener(this); + mRecyclerRefresh.setEnabled(false); + + mRecycler.setAdapter(mAdapter = new NearbyUserAdapter(this)); + mAdapter.setOnItemClickListener(this); + } + + @Override + protected void initData() { + initLbs(); + requestLocationPermission(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_nearby_more, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_item_more: + getSelectorDialog().show(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + releaseLbs(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if ((resultCode == RESULT_OK || resultCode == RESULT_CANCELED) + && requestCode == LOCATION_PERMISSION) { + startLbs(); + } + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.tv_clear_opt: + clearLbsUserInfo(); + if (mSelectorDialog.isShowing()) + mSelectorDialog.cancel(); + break; + case R.id.tv_cancel_opt: + if (mSelectorDialog.isShowing()) + mSelectorDialog.cancel(); + break; + } + } + + /** + * 附近的人的监听 + * + * @param result Radar Nearby Result + * @param error Radar Search Error + */ + @Override + public void onGetNearbyInfoList(RadarNearbyResult result, RadarSearchError error) { + updateView(result); + } + + /** + * 上传用户信息的监听 + * + * @param error Radar Search Error + */ + @Override + public void onGetUploadState(RadarSearchError error) { + switch (error) { + case RADAR_NETWORK_ERROR: + case RADAR_NETWORK_TIMEOUT: + if (mNextPageIndex == 0) { + showError(EmptyLayout.NETWORK_ERROR); + mEmptyLayout.setNoDataContent(getString(R.string.network_timeout_hint)); + } else { + AppContext.showToastShort(R.string.request_error_hint); + } + SimplexToast.show(this, getString(R.string.upload_lbs_info_hint)); + break; + case RADAR_NO_ERROR: + if (mIsFirstLocation) { + Setting.updateLocationInfo(getApplicationContext(), true); + onRefreshing(); + } + break; + case RADAR_PERMISSION_UNFINISHED: + ShowSettingDialog(); + break; + } + } + + /** + * 清除用户信息的监听 + * + * @param error Radar Search Error + */ + @Override + public void onGetClearInfoState(RadarSearchError error) { + switch (error) { + case RADAR_NO_RESULT://未上传有雷达信息 + Setting.updateLocationInfo(getApplicationContext(), false); + break; + case RADAR_NO_ERROR://清除雷达信息成功 + Setting.updateLocationInfo(getApplicationContext(), false); + supportFinishAfterTransition(); + break; + default: + SimplexToast.show(this, getString(R.string.clear_bodies_failed_hint)); + break; + } + } + + /** + * lbs callback + * + * @param location location + */ + @Override + public void onReceiveLocation(BDLocation location) { + ReceiveLocation(location); + } + + @Override + public void onRefreshing() { + requestData(0); + } + + @Override + public void onLoadMore() { + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + requestData(mNextPageIndex); + } + + @Override + public void onScrollToBottom() { + + } + + @Override + public void onItemClick(int position, long itemId) { + NearbyResult result = mAdapter.getItem(position); + if (result == null) return; + OtherUserHomeActivity.show(this, result.getUser()); + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + ShowSettingDialog(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + /** + * proxy request permission + */ + @AfterPermissionGranted(LOCATION_PERMISSION) + private void requestLocationPermission() { + + if (isEnabledLocation()) { + if (EasyPermissions.hasPermissions(this, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.READ_PHONE_STATE)) { + startLbs(); + } else { + EasyPermissions.requestPermissions(this, getString(R.string.need_lbs_permission_hint), LOCATION_PERMISSION, + Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.READ_PHONE_STATE); + } + } + } + + /** + * menu action selector Dialog + * + * @return dialog + */ + private Dialog getSelectorDialog() { + if (mSelectorDialog == null) { + mSelectorDialog = new BottomDialog(this, true); + @SuppressLint("InflateParams") View view = LayoutInflater.from(this).inflate(R.layout.view_nearby_operator, null, false); + view.findViewById(R.id.tv_clear_opt).setOnClickListener(this); + view.findViewById(R.id.tv_cancel_opt).setOnClickListener(this); + mSelectorDialog.setContentView(view); + ViewGroup parent = (ViewGroup) view.getParent(); + if (parent != null) { + parent.setBackgroundResource(R.color.transparent); + } + } + return mSelectorDialog; + } + + /** + * update data view + * + * @param result radarNearByResult + */ + private void updateView(RadarNearbyResult result) { + mRecyclerRefresh.onComplete(); + if (result != null) { + //pageNum==0,表示初始化数据,有可能是刷新,也有可能是第一次加载 + List infoList = result.infoList; + int pageIndex = result.pageIndex; + if (infoList != null && infoList.size() != 0) { + List items = mAdapter.getItems(); + + int tempSize = items.size(); + + if (pageIndex == 0) { + //没有缓存数据,直接添加 + for (RadarNearbyInfo info : infoList) { + User user = null; + try { + String comments = URLDecoder.decode(info.comments, CHARSET); + user = AppOperator.createGson().fromJson(comments, User.class); + } catch (Exception e) { + e.printStackTrace(); + } + + if (user == null || (user.getId() == 0 && TextUtils.isEmpty(user.getName()))) + continue; + + NearbyResult.Nearby nearby = new NearbyResult.Nearby(); + nearby.setDistance(info.distance); + nearby.setMobileName(info.mobileName); + nearby.setMobileOS(info.mobileOS); + items.add(new NearbyResult(user, nearby)); + } + + //根据数据的距离从近到远进行排序 + notifySortData(pageIndex, infoList); + } else { + //当pageNum>0时,证明是翻页,不管是否有缓存,直接添加 + for (RadarNearbyInfo info : infoList) { + User user = null; + try { + String comments = URLDecoder.decode(info.comments, CHARSET); + user = AppOperator.createGson().fromJson(comments, User.class); + } catch (Exception e) { + e.printStackTrace(); + } + + if (user == null || (user.getId() == 0 && TextUtils.isEmpty(user.getName()))) + continue; + + int index = containsFriend(user); + + if (index == -1) { + NearbyResult.Nearby nearby = new NearbyResult.Nearby(); + nearby.setDistance(info.distance); + nearby.setMobileName(info.mobileName); + nearby.setMobileOS(info.mobileOS); + items.add(new NearbyResult(user, nearby)); + } + } + + if (tempSize < items.size()) { + //根据数据的距离从近到远进行排序 + notifySortData(pageIndex, infoList); + } + } + } else { + //没有数据返回时,不管pageIndex是多少,保证nextPageIndex不变与缓存不变 + notifyNoData(null); + } + } else { + //2.请求结果为null,不管pageIndex如何,保持缓存不变 + notifyNoData(null); + } + + } + + /** + * clear lbs user info + */ + private void clearLbsUserInfo() { + //清除用户信息 + if (mRadarSearchManager == null) return; + mRadarSearchManager.clearUserInfo(); + Setting.updateLocationInfo(getApplicationContext(), false); + } + + /** + * notify no data + */ + private void notifyNoData(List infoList) { + if (infoList == null) { + //没有缓存直接进行提示 + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + } + if (mAdapter != null && mAdapter.getItems().size() == 0 && mEmptyLayout != null) { + mEmptyLayout.setErrorType(EmptyLayout.NODATA); + } else { + hideLoading(); + } + } + + /** + * notify sort data + * + * @param pageIndex request page index + */ + private void notifySortData(int pageIndex, List infoList) { + //根据数据的距离从近到远进行排序 + //Collections.sort(items); + //刷新数据,初始化有效数据ui + mAdapter.notifyDataSetChanged(); + mAdapter.setState(infoList == null ? BaseRecyclerAdapter.STATE_NO_MORE : BaseRecyclerAdapter.STATE_LOAD_MORE, true); + //隐藏emptyView + hideLoading(); + mNextPageIndex = (pageIndex + 1); + } + + /** + * check is cache + * + * @param user load_user + * @return isCache?index:-1 + */ + private int containsFriend(User user) { + int index = -1; + List items = this.mAdapter.getItems(); + for (int i = 0; i < items.size(); i++) { + if (items.get(i).getUser().getId() == user.getId()) { + index = i; + break; + } + } + return index; + } + + /** + * show setting dialog + */ + private void ShowSettingDialog() { + + if (confirmDialog == null) { + confirmDialog = DialogHelper.getConfirmDialog(this, getString(R.string.location_get_failed_hint), + getString(R.string.no_permission_hint), + getString(R.string.cancel), getString(R.string.actionbar_title_setting), false, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + clearLbsUserInfo(); + supportFinishAfterTransition(); + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); + //intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivityForResult(intent, LOCATION_PERMISSION); + } + }); + } + + if (alertDialog == null) { + alertDialog = confirmDialog.create(); + } + + if (alertDialog != null) { + if (!alertDialog.isShowing()) { + alertDialog.show(); + } + } + } + + /** + * 定位回调处理 + * + * @param location location + */ + private void ReceiveLocation(BDLocation location) { + final int code = location.getLocType(); + switch (code) { + case BDLocation.TypeCriteriaException://62 + showError(EmptyLayout.NODATA); + SimplexToast.show(this, getString(R.string.no_location_hint)); + mLocationClient.start(); + mRecyclerRefresh.setOnLoading(false); + return; + case BDLocation.TypeNetWorkException://63 + if (mNextPageIndex == 0) { + showError(EmptyLayout.NETWORK_ERROR); + } + mRecyclerRefresh.setOnLoading(false); + SimplexToast.show(this, getString(R.string.network_exception_hint)); + return; + case BDLocation.TypeServerError://167 + if (isEnabledLocation()) { + ShowSettingDialog(); + } + hideLoading(); + showError(EmptyLayout.NODATA); + mRecyclerRefresh.setOnLoading(false); + SimplexToast.show(this, getString(R.string.server_no_have_permission_hint)); + return; + case BDLocation.TypeNetWorkLocation://161 + + if (!TDevice.hasInternet() && mNextPageIndex == 0 && mAdapter.getCount() <= 0) { + showError(EmptyLayout.NETWORK_ERROR); + } + break; + case BDLocation.TypeOffLineLocation://66 离线模式 + + if (!TDevice.hasInternet()) { + showError(EmptyLayout.NETWORK_ERROR); + SimplexToast.show(this, getString(R.string.tip_network_error)); + return; + } + + break; + } + + if (code >= 501) { + showError(EmptyLayout.NODATA); + SimplexToast.show(this, getString(R.string.key_is_invalid_hint)); + return; + } + + if (TDevice.hasInternet() && location.getLatitude() != 4.9E-324 && location.getLongitude() != 4.9E-324) { + + boolean started = mLocationClient.isStarted(); + + if (started) { + mLocationClient.stop(); + } + + mUserLatLng = new LatLng(location.getLatitude(), location.getLongitude()); + + Setting.updateLocationPermission(getApplicationContext(), true); + + //周边雷达设置用户身份标识,id为空默认是设备标识 + String userId = null; + + //上传位置 + RadarUploadInfo info = new RadarUploadInfo(); + + if (AccountHelper.isLogin()) { + userId = String.valueOf(AccountHelper.getUserId()); + + User user = AccountHelper.getUser(); + try { + SampleAuthor author = new SampleAuthor(user); + String authorJson = AppOperator.getGson().toJson(author); + info.comments = URLEncoder.encode(authorJson, CHARSET); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + SimplexToast.show(this, getString(R.string.upload_lbs_info_hint)); + } + } + + mRadarSearchManager.setUserID(userId); + info.pt = mUserLatLng; + mRadarSearchManager.uploadInfoRequest(info); + } else { + if (!TDevice.hasInternet() && mNextPageIndex == 0 && mAdapter.getCount() <= 0) { + showError(EmptyLayout.NETWORK_ERROR); + } + } + } + + private static class SampleAuthor { + public long id; + public String name; + public String portrait; + public int gender; + public Author.Identity identity; + public SampleAuthorMore more; + + private SampleAuthor(User user) { + this.id = user.getId(); + this.name = user.getName(); + this.portrait = user.getPortrait(); + this.gender = user.getGender(); + this.identity = user.getIdentity(); + this.more = new SampleAuthorMore(user.getMore() != null ? user.getMore().getCompany() : ""); + if (this.identity == null) + this.identity = new Author.Identity(); + } + } + + private static class SampleAuthorMore { + public String company; + + private SampleAuthorMore(String company) { + company = company == null ? "" : company; + this.company = company; + } + } + + /** + * 判断系统定位功能是否打开,如果未打开直接跳转去打开 + * + * @return true/false + */ + private boolean isEnabledLocation() { + + LocationManager locationManager = this.mLocationManager; + + if (mLocationManager == null) { + locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); + this.mLocationManager = locationManager; + } + + if (locationManager != null) { + + //gps 基于gps定位 + boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + + //network 基于wifi和基站定位 + boolean netEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); + + //passive 基于被动的精度不高,或者不怎么变化的位置服务,比如其他应用或者定位服务位置更新时的定位 + //boolean passiveEnabled = locationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER); + + if (gpsEnabled || netEnabled) { + return true; + } else { + ShowSettingDialog(); + return false; + } + + } else { + showError(EmptyLayout.NODATA); + AppContext.showToastShort(R.string.near_body_gps_error_hint); + supportFinishAfterTransition(); + return false; + } + } + + /** + * start auto lbs service + */ + private void startLbs() { + if (mRadarSearchManager == null || mLocationClient == null) { + initLbs(); + } + //进行定位 + mLocationClient.restart(); + } + + /** + * init lbs service + */ + private void initLbs() { + if (mRadarSearchManager == null) { + mRadarSearchManager = RadarSearchManager.getInstance(); + mRadarSearchManager.addNearbyInfoListener(this); + } + + if (mLocationClient == null) { + mLocationClient = new LocationClient(this); + mLocationClient.registerLocationListener(this); + } + + LocationClientOption option = new LocationClientOption(); + + //可选,默认高精度,设置定位模式,高精度,低功耗,仅设备 + option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy); + + //可选,默认gcj02,设置返回的定位结果坐标系 + option.setCoorType("bd09ll"); + + //根据网络情况和gps进行准确定位,7s定位一次 当获取到真实有效的经纬度时,主动关闭定位功能 + option.setScanSpan(7 * 1000); + + //可选,设置是否需要地址信息,默认不需要 + option.setIsNeedAddress(false); + + //设置是否需要位置语义化结果 + option.setIsNeedLocationDescribe(false); + + //可选,默认true,定位SDK内部是一个SERVICE,并放到了独立进程,设置是否在stop的时候杀死这个进程,默认不杀死 + //option.setIgnoreKillProcess(false); + + //可选,默认false,设置是否收集CRASH信息,默认收集 + option.SetIgnoreCacheException(false); + + mLocationClient.setLocOption(option); + } + + /** + * release lbs source + */ + private void releaseLbs() { + if (mLocationClient != null && mLocationClient.isStarted()) + mLocationClient.stop(); + //移除监听 + if (mRadarSearchManager != null) { + mRadarSearchManager.removeNearbyInfoListener(this); + //释放资源 + mRadarSearchManager.destroy(); + mRadarSearchManager = null; + } + } + + /** + * request data + * + * @param pageIndex pageIndex + */ + private void requestData(int pageIndex) { + + if (TDevice.hasInternet()) { + + if (pageIndex == 0 && mAdapter.getCount() <= 0) { + //if (mEmptyLayout.getVisibility() == View.VISIBLE) + mEmptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + } + + if (mUserLatLng == null || (mUserLatLng.latitude == 4.9E-324 && mUserLatLng.longitude == 4.9E-324)) { + mLocationClient.start(); + return; + } + + //构造请求参数,其中centerPt是自己的位置坐标 + RadarNearbySearchOption option = new RadarNearbySearchOption() + .centerPt(mUserLatLng).pageNum(pageIndex).radius(38000).pageCapacity(50). + sortType(RadarNearbySearchSortType.distance_from_far_to_near); + //发起查询请求 + mRadarSearchManager.nearbyInfoRequest(option); + mIsFirstLocation = false; + + } else { + if (pageIndex == 0) { + if (mAdapter.getCount() > 0) { + mAdapter.setState(BaseRecyclerAdapter.STATE_INVALID_NETWORK, false); + AppContext.showToastShort(R.string.error_view_network_error_click_to_refresh); + mRecyclerRefresh.onComplete(); + } else { + showError(EmptyLayout.NETWORK_ERROR); + AppContext.showToastShort(R.string.error_view_network_error_click_to_refresh); + } + } else { + mRecyclerRefresh.onComplete(); + AppContext.showToastShort(R.string.error_view_network_error_click_to_refresh); + } + } + + } + + /** + * hide empty view's loading + */ + private void hideLoading() { + final EmptyLayout emptyLayout = mEmptyLayout; + + if (emptyLayout == null) + return; + + if (emptyLayout.getVisibility() == View.VISIBLE) { + Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_alpha_to_hide); + animation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + + } + + @Override + public void onAnimationEnd(Animation animation) { + emptyLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }); + + emptyLayout.startAnimation(animation); + } + } + + /** + * show empty view's error type + * + * @param type type + */ + private void showError(int type) { + EmptyLayout layout = mEmptyLayout; + if (layout != null) { + layout.setErrorType(type); + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/search/activities/SearchActivity.java b/app/src/main/java/net/oschina/app/improve/search/activities/SearchActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..cb133d84559d98da24c1d08c86c37cfd02eb773d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/activities/SearchActivity.java @@ -0,0 +1,278 @@ +package net.oschina.app.improve.search.activities; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.util.Pair; +import android.support.v4.view.ViewPager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.SearchView; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.search.adapters.SearchHistoryAdapter; +import net.oschina.app.improve.search.fragments.SearchArticleFragment; +import net.oschina.app.improve.search.fragments.SearchUserFragment; +import net.oschina.app.improve.utils.CacheManager; +import net.oschina.app.improve.utils.DialogHelper; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 搜索界面 + * Created by thanatos on 16/9/7. + */ + +@SuppressWarnings("RestrictedApi") +public class SearchActivity extends BaseActivity implements ViewPager.OnPageChangeListener { + + @Bind(R.id.view_root) + LinearLayout mViewRoot; + @Bind(R.id.layout_tab) + TabLayout mLayoutTab; + @Bind(R.id.view_pager) + ViewPager mViewPager; + @Bind(R.id.view_searcher) + SearchView mViewSearch; + @Bind(R.id.search_mag_icon) + ImageView mSearchIcon; + @Bind(R.id.search_edit_frame) + LinearLayout mLayoutEditFrame; + @Bind(R.id.search_src_text) + EditText mViewSearchEditor; + @Bind(R.id.recyclerView) + RecyclerView mRecyclerView; + + private SearchHistoryAdapter mAdapter; + + private static final String CACHE_NAME = "search_history"; + private List> mPagerItems; + private String mSearchText; + private Runnable mSearchRunnable = new Runnable() { + @Override + public void run() { + if (TextUtils.isEmpty(mSearchText)) + return; + SearchAction f = (SearchAction) mPagerItems.get(mViewPager.getCurrentItem()).second; + f.search(mSearchText); + SearchHistoryAdapter.SearchItem item = new SearchHistoryAdapter.SearchItem(mSearchText); + if (mAdapter.getItems().contains(item)) { + mAdapter.removeItem(item); + } + mAdapter.addItem(0, item); + mRecyclerView.scrollToPosition(0); + SearchHistoryAdapter.SearchItem last = mAdapter.getItem(mAdapter.getItems().size() - 1); + if (last != null && last.getType() == 0) { + mAdapter.addItem(new SearchHistoryAdapter.SearchItem("清空搜索历史", 1)); + } + } + }; + + public static void show(Context context) { + Intent intent = new Intent(context, SearchActivity.class); + context.startActivity(intent); + } + + + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + // pass + } + + @Override + public void onPageSelected(int position) { + String content = mSearchText; + if (TextUtils.isEmpty(content)) return; + doSearch(content); + mViewSearch.clearFocus(); + } + + @Override + public void onPageScrollStateChanged(int state) { + // pass + } + + public interface SearchAction { + void search(String content); + } + + @Override + protected int getContentView() { + return R.layout.activity_v2_search; + } + + @Override + protected void initWindow() { + mPagerItems = new ArrayList<>(); + + mPagerItems.add(new Pair<>("软件", SearchArticleFragment.instantiate(this, News.TYPE_SOFTWARE))); + mPagerItems.add(new Pair<>("博客", SearchArticleFragment.instantiate(this, News.TYPE_BLOG))); + mPagerItems.add(new Pair<>("资讯", SearchArticleFragment.instantiate(this, News.TYPE_NEWS))); + mPagerItems.add(new Pair<>("问答", SearchArticleFragment.instantiate(this, News.TYPE_QUESTION))); + mPagerItems.add(new Pair<>("找人", SearchUserFragment.instantiate(this))); + + } + + @Override + protected void onPause() { + mViewSearch.clearFocus(); + super.onPause(); + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + mAdapter = new SearchHistoryAdapter(this); + mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + mRecyclerView.setAdapter(mAdapter); + List items = CacheManager.readListJson(this, CACHE_NAME, SearchHistoryAdapter.SearchItem.class); + mAdapter.addAll(items); + if (mAdapter.getItems().size() != 0) { + mAdapter.addItem(new SearchHistoryAdapter.SearchItem("清空搜索历史", 1)); + } + mRecyclerView.setAnimation(null); + mAdapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + SearchHistoryAdapter.SearchItem item = mAdapter.getItem(position); + if (item != null && item.getType() == 0) { + mViewSearch.clearFocus(); + String query = item.getSearchText(); + mViewSearchEditor.setText(query); + mViewSearchEditor.setSelection(query.length()); + doSearch(query); + } else { + DialogHelper.getConfirmDialog(SearchActivity.this, "提示", "确认清空搜索历史记录吗?", "确认", "取消", true, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mAdapter.clear(); + } + }).show(); + } + } + }); + mViewSearchEditor.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + mViewSearch.setOnCloseListener(new SearchView.OnCloseListener() { + @Override + public boolean onClose() { + // 阻止点击关闭按钮 collapse icon + return true; + } + }); + mViewSearch.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + mViewSearch.clearFocus(); + return doSearch(query); + } + + @Override + public boolean onQueryTextChange(String newText) { + if (TextUtils.isEmpty(newText)) { + doSearch(""); + } + return false; + } + }); + + mViewPager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) { + @Override + public Fragment getItem(int position) { + return mPagerItems.get(position).second; + } + + @Override + public int getCount() { + return mPagerItems.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + return mPagerItems.get(position).first; + } + }); + mViewPager.addOnPageChangeListener(this); + mViewPager.setOffscreenPageLimit(0); + mLayoutTab.setupWithViewPager(mViewPager); + + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mSearchIcon.getLayoutParams(); + params.width = ViewGroup.LayoutParams.WRAP_CONTENT; + mSearchIcon.setLayoutParams(params); + + LinearLayout.LayoutParams params1 = (LinearLayout.LayoutParams) mLayoutEditFrame.getLayoutParams(); + params1.setMargins(0, 0, 0, 0); + mLayoutEditFrame.setLayoutParams(params1); + + mViewSearch.post(new Runnable() { + @Override + public void run() { +// TDevice.showSoftKeyboard(mViewSearch); + mViewSearch.setIconified(false); + } + }); + } + + + private boolean doSearch(String query) { + mSearchText = query.trim(); + // Always cancel all request + mViewPager.removeCallbacks(mSearchRunnable); + // Search is'nt empty + if (TextUtils.isEmpty(mSearchText)) { + mLayoutTab.setVisibility(View.GONE); + mViewPager.setVisibility(View.GONE); + mRecyclerView.setVisibility(View.VISIBLE); + return false; + } + + mLayoutTab.setVisibility(View.VISIBLE); + mViewPager.setVisibility(View.VISIBLE); + mRecyclerView.setVisibility(View.GONE); + + // In this we delay 1 seconds + mViewPager.postDelayed(mSearchRunnable, 0); + return true; + } + + @OnClick(R.id.tv_search) + void onClickCancel() { + //onBackPressed(); + doSearch(mViewSearch.getQuery().toString()); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + finish(); + } + + @Override + protected void onDestroy() { + SearchHistoryAdapter.SearchItem last = mAdapter.getItem(mAdapter.getItems().size() - 1); + if (last != null && last.getType() != 0) { + mAdapter.removeItem(last); + } + CacheManager.saveToJson(this, CACHE_NAME, mAdapter.getItems()); + super.onDestroy(); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/search/adapters/NearbyUserAdapter.java b/app/src/main/java/net/oschina/app/improve/search/adapters/NearbyUserAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..0f0e01addf621461620d8731edb6ef3ac9e7678c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/adapters/NearbyUserAdapter.java @@ -0,0 +1,102 @@ +package net.oschina.app.improve.search.adapters; + +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 net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.NearbyResult; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * Created by thanatos + * on 16/10/24. + * Updated by fei + * on 17/01/12. + */ + +public class NearbyUserAdapter extends BaseRecyclerAdapter { + + public NearbyUserAdapter(Context context) { + super(context, ONLY_FOOTER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_nearby_user, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder h, NearbyResult item, int position) { + + if (h instanceof ViewHolder) { + ViewHolder holder = (ViewHolder) h; + holder.mIdentityView.setup(item.getUser()); + if (item.getUser() != null) { + holder.mViewPortrait.setup(item.getUser()); + holder.mViewNick.setText(item.getUser().getName()); + holder.mViewGender.setVisibility(View.VISIBLE); + switch (item.getUser().getGender()) { + case User.GENDER_FEMALE: + holder.mViewGender.setImageResource(R.mipmap.ic_female); + break; + case User.GENDER_MALE: + holder.mViewGender.setImageResource(R.mipmap.ic_male); + break; + default: + holder.mViewGender.setVisibility(View.GONE); + } + User.More more = item.getUser().getMore(); + if (more != null) { + holder.mViewPosition.setText(more.getCompany()); + } else { + holder.mViewPosition.setText("??? ???"); + } + } else { + holder.mViewPortrait.setup(0, "?", ""); + holder.mViewNick.setText("???"); + holder.mViewGender.setVisibility(View.GONE); + holder.mViewPosition.setText("??? ???"); + } + + if (item.getNearby() != null) { + holder.mViewDistance.setText(StringUtils.formatDistance(item.getNearby().getDistance())); + } else { + holder.mViewDistance.setText("未知距离"); + } + } + + } + + static class ViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.identityView) + IdentityView mIdentityView; + @Bind(R.id.iv_portrait) + PortraitView mViewPortrait; + @Bind(R.id.tv_nick) + TextView mViewNick; + @Bind(R.id.tv_position) + TextView mViewPosition; + @Bind(R.id.tv_distance) + TextView mViewDistance; + @Bind(R.id.iv_gender) + ImageView mViewGender; + + public ViewHolder(View view) { + super(view); + ButterKnife.bind(this, view); + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/search/adapters/SearchArticleAdapter.java b/app/src/main/java/net/oschina/app/improve/search/adapters/SearchArticleAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..827375b71c5b6385329b578cda9571d3bf0187a1 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/adapters/SearchArticleAdapter.java @@ -0,0 +1,57 @@ +package net.oschina.app.improve.search.adapters; + +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.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.News; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * Created by thanatos on 16/10/24. + */ + +public class SearchArticleAdapter extends BaseRecyclerAdapter { + + public SearchArticleAdapter(Context context) { + this(context, ONLY_FOOTER); + } + + public SearchArticleAdapter(Context context, int mode) { + super(context, mode); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ViewHolder(LayoutInflater.from(mContext) + .inflate(R.layout.list_item_search_article, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder h, News item, int position) { + ViewHolder holder = (ViewHolder) h; + holder.mViewTitle.setText(item.getTitle()); + holder.mViewContent.setText(item.getBody()); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + + @Bind(R.id.tv_title) + TextView mViewTitle; + @Bind(R.id.tv_content) + TextView mViewContent; + + public ViewHolder(View view) { + super(view); + ButterKnife.bind(this, view); + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/search/adapters/SearchHistoryAdapter.java b/app/src/main/java/net/oschina/app/improve/search/adapters/SearchHistoryAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..635cf8467d0812412271457c876bece0a6580a45 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/adapters/SearchHistoryAdapter.java @@ -0,0 +1,103 @@ +package net.oschina.app.improve.search.adapters; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; + +/** + * Created by haibin + * on 17/2/14. + */ + +public class SearchHistoryAdapter extends BaseRecyclerAdapter { + private static final int VIEW_TYPE_CLEAR = 3; + + public SearchHistoryAdapter(Context context) { + super(context, NEITHER); + } + + + @Override + public int getItemViewType(int position) { + if (mItems.get(position).getType() != 0) + return VIEW_TYPE_CLEAR; + return super.getItemViewType(position); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + if (type == VIEW_TYPE_CLEAR) + return new ClearViewHolder(mInflater.inflate(R.layout.item_list_clear_search, null)); + return new SearchViewHolder(mInflater.inflate(R.layout.item_list_search_history, null)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, SearchItem item, int position) { + if (holder.getItemViewType() != VIEW_TYPE_CLEAR) { + SearchViewHolder h = (SearchViewHolder) holder; + h.mTextTitle.setText(item.getSearchText()); + } + } + + private class SearchViewHolder extends RecyclerView.ViewHolder { + TextView mTextTitle; + + SearchViewHolder(View itemView) { + super(itemView); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_search); + } + } + + private class ClearViewHolder extends RecyclerView.ViewHolder { + TextView mTextTitle; + + ClearViewHolder(View itemView) { + super(itemView); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_search); + } + } + + public static class SearchItem { + private String searchText; + private int type; + + public SearchItem(String item) { + this.searchText = item; + } + + + public SearchItem(String searchText, int type) { + this.searchText = searchText; + this.type = type; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getSearchText() { + return searchText; + } + + public void setSearchText(String searchText) { + this.searchText = searchText; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof SearchItem && obj != null) { + return searchText.equals(((SearchItem) obj).searchText); + } + return super.equals(obj); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/search/adapters/SearchUserAdapter.java b/app/src/main/java/net/oschina/app/improve/search/adapters/SearchUserAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..71aba8b4299d3c25c9c05b50f3229c07e04b8d32 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/adapters/SearchUserAdapter.java @@ -0,0 +1,72 @@ +package net.oschina.app.improve.search.adapters; + +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.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * Created by thanatos on 16/10/24. + */ + +public class SearchUserAdapter extends BaseRecyclerAdapter { + + public SearchUserAdapter(Context context) { + this(context, ONLY_FOOTER); + } + + public SearchUserAdapter(Context context, int mode) { + super(context, mode); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ViewHolder(LayoutInflater.from(mContext) + .inflate(R.layout.list_item_search_person, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder h, User item, int position) { + ViewHolder holder = (ViewHolder) h; + holder.mIdentityView.setup(item); + holder.mViewPortrait.setup(item); + holder.mViewNick.setText(item.getName()); + holder.mViewPosition.setText(String.format("%s %s %s", + item.getMore().getCompany(), item.getMore().getPosition(), item.getMore().getCity()) + .replaceAll("null", "") + .trim() + ); + holder.mViewIntegral.setText(String.format("积分 %s | 关注 %s | 粉丝 %s", + item.getStatistics().getScore(), item.getStatistics().getFollow(), item.getStatistics().getFans())); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.identityView) + IdentityView mIdentityView; + @Bind(R.id.iv_portrait) + PortraitView mViewPortrait; + @Bind(R.id.tv_nick) + TextView mViewNick; + @Bind(R.id.tv_position) + TextView mViewPosition; + @Bind(R.id.tv_integral) + TextView mViewIntegral; + + public ViewHolder(View view) { + super(view); + ButterKnife.bind(this, view); + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/search/fragments/SearchArticleFragment.java b/app/src/main/java/net/oschina/app/improve/search/fragments/SearchArticleFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..a9c8bd5eb732ffefa1077101ae1fe3795c5e2ca5 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/fragments/SearchArticleFragment.java @@ -0,0 +1,127 @@ +package net.oschina.app.improve.search.fragments; + +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.TextUtils; +import android.view.View; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseRecyclerViewFragment; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.search.activities.SearchActivity; +import net.oschina.app.improve.search.adapters.SearchArticleAdapter; +import net.oschina.app.util.UIHelper; + +import java.lang.reflect.Type; + +/** + * 博客、软件、资讯、问答的搜索界面 + * Created by thanatos on 16/10/18. + */ + +public class SearchArticleFragment extends BaseRecyclerViewFragment + implements SearchActivity.SearchAction { + + public static final String BUNDLE_KEY_CATALOG = "BUNDLE_KEY_CATALOG"; + + private int catalog = News.TYPE_NEWS; + private String content; + private boolean isRequesting; + + public static Fragment instantiate(Context context, int catalog) { + Fragment fragment = new SearchArticleFragment(); + Bundle bundle = new Bundle(); + bundle.putInt(BUNDLE_KEY_CATALOG, catalog); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + catalog = bundle.getInt(BUNDLE_KEY_CATALOG, News.TYPE_NEWS); + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mRefreshLayout.setRefreshing(false); + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + return new SearchArticleAdapter(getContext()); + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } + + @Override + protected void requestData() { + super.requestData(); + if (TextUtils.isEmpty(content)) { + mRefreshLayout.setRefreshing(false); + return; + } + if (isRequesting) return; + isRequesting = true; + String token = isRefreshing ? null : mBean.getNextPageToken(); + OSChinaApi.search(catalog, content, token, mHandler); + } + + @Override + protected void onRequestFinish() { + super.onRequestFinish(); + isRequesting = false; + } + + @Override + public void onItemClick(int position, long itemId) { + super.onItemClick(position, itemId); + News item = mAdapter.getItem(position); + if (item == null) return; + switch (item.getType()) { + case News.TYPE_BLOG: + BlogDetailActivity.show(getContext(), item.getId()); + break; + case News.TYPE_QUESTION: + QuestionDetailActivity.show(getContext(), item.getId()); + break; + case News.TYPE_SOFTWARE: + SoftwareDetailActivity.show(getContext(), item.getId()); + break; + case News.TYPE_NEWS: + NewsDetailActivity.show(getContext(), item.getId()); + break; + default: + UIHelper.openInternalBrowser(getContext(), item.getHref()); + } + } + + @Override + public void search(String content) { + if (this.content != null && this.content.equals(content)) return; + this.content = content; + mAdapter.clear(); + mRefreshLayout.setRefreshing(true); + onRefreshing(); + } + + @Override + protected boolean isNeedEmptyView() { + return false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/search/fragments/SearchUserFragment.java b/app/src/main/java/net/oschina/app/improve/search/fragments/SearchUserFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..84a01aa87d6d51e20650562692c62213956c392d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/fragments/SearchUserFragment.java @@ -0,0 +1,101 @@ +package net.oschina.app.improve.search.fragments; + +import android.content.Context; +import android.support.v4.app.Fragment; +import android.text.TextUtils; +import android.util.Log; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseRecyclerViewFragment; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.search.activities.SearchActivity; +import net.oschina.app.improve.search.adapters.SearchUserAdapter; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; + +import java.lang.reflect.Type; + +/** + * 搜索人界面 + * Created by thanatos + * on 16/10/24. + */ +public class SearchUserFragment extends BaseRecyclerViewFragment + implements SearchActivity.SearchAction { + + private String content; + + // fix: 直接点击找人Tab的时候,doSearch调用requestData方法, 异步网络过程中, + // 生命周期流程调用requestData, 前一次调用使isRefreshing致为false,导致数据重复 + private boolean isRequesting = false; + + public static Fragment instantiate(Context context) { + return new SearchUserFragment(); + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + return new SearchUserAdapter(getContext()); + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } + + @Override + protected void requestData() { + super.requestData(); + Log.i("thanatosx", "Search User Fragment Request Data, Content: " + content); + if (TextUtils.isEmpty(content)) { + mRefreshLayout.setRefreshing(false); + return; + } + if (isRequesting) return; + isRequesting = true; + String token = isRefreshing ? null : mBean.getNextPageToken(); + OSChinaApi.search(News.TYPE_FIND_PERSON, content, token, mHandler); + } + + @Override + protected void onRequestFinish() { + super.onRequestFinish(); + isRequesting = false; + } + + @Override + public void onItemClick(int position, long itemId) { + super.onItemClick(position, itemId); + User user = mAdapter.getItem(position); + if (user == null) return; + OtherUserHomeActivity.show(getContext(), user.getId()); + } + + @Override + public void search(String content) { + Log.i("thanatosx", "Search User Fragment Do Search, Content: " + content); + if (this.content != null && this.content.equals(content)) return; + this.content = content; + if(mRecyclerView == null) + return; + mAdapter.clear(); + mRefreshLayout.setRefreshing(true); + onRefreshing(); + } + + @Override + protected boolean isNeedEmptyView() { + return false; + } + + @Override + protected boolean isNeedCache() { + return false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/search/software/SearchSoftwareActivity.java b/app/src/main/java/net/oschina/app/improve/search/software/SearchSoftwareActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..08cfedc8f853d8b3b68f927bde71a843d50b6109 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/software/SearchSoftwareActivity.java @@ -0,0 +1,106 @@ +package net.oschina.app.improve.search.software; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.SearchView; +import android.util.TypedValue; +import android.view.View; +import android.widget.EditText; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.TDevice; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 只搜索软件 + * Created by huanghaibin on 2018/1/5. + */ +public class SearchSoftwareActivity extends BackActivity implements + View.OnClickListener, + SearchSoftwareContract.ActionView { + @Bind(R.id.view_searcher) + SearchView mViewSearch; + @Bind(R.id.search_src_text) + EditText mViewSearchEditor; + private SearchSoftwarePresenter mPresenter; + + public static void show(Context context, String keyword) { + Intent intent = new Intent(context, SearchSoftwareActivity.class); + intent.putExtra("keyword", keyword); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_search_software; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + + mViewSearch.setOnCloseListener(new SearchView.OnCloseListener() { + @Override + public boolean onClose() { + // 阻止点击关闭按钮 collapse icon + return true; + } + }); + mViewSearch.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + TDevice.closeKeyboard(mViewSearchEditor); + mPresenter.mKeyword = query; + mPresenter.onRefreshing(); + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + return false; + } + }); + + mViewSearchEditor.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + + String keyword = getIntent().getStringExtra("keyword"); + mViewSearchEditor.setText(keyword); + + SearchSoftwareFragment fragment = SearchSoftwareFragment.newInstance(); + addFragment(R.id.fl_content, fragment); + mPresenter = new SearchSoftwarePresenter(fragment, this); + mPresenter.mKeyword = keyword; + } + + @OnClick({R.id.tv_search}) + @Override + public void onClick(View view) { + if (mPresenter == null) + return; + TDevice.closeKeyboard(mViewSearchEditor); + mPresenter.mKeyword = mViewSearchEditor.getText().toString().trim(); + mPresenter.onRefreshing(); + } + + @Override + public void showSearchFailure(int strId) { + if (isDestroyed()) { + return; + } + SimplexToast.show(this, strId); + } + + @Override + public void showSearchFailure(String str) { + if (isDestroyed()) { + return; + } + SimplexToast.show(this, str); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/search/software/SearchSoftwareContract.java b/app/src/main/java/net/oschina/app/improve/search/software/SearchSoftwareContract.java new file mode 100644 index 0000000000000000000000000000000000000000..dfc9cfe0b6588af2fa0308ad699387d7033b6033 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/software/SearchSoftwareContract.java @@ -0,0 +1,28 @@ +package net.oschina.app.improve.search.software; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.base.BaseView; +import net.oschina.app.improve.bean.Article; + +/** + * 软件搜索 + * Created by huanghaibin on 2018/1/5. + */ + +interface SearchSoftwareContract { + + interface ActionView { + void showSearchFailure(int strId); + + void showSearchFailure(String str); + } + + interface View extends BaseListView { + + } + + interface Presenter extends BaseListPresenter { + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/search/software/SearchSoftwareFragment.java b/app/src/main/java/net/oschina/app/improve/search/software/SearchSoftwareFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..63d60bfaf57fe10b2d3c8dc47dc3626e663fae47 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/software/SearchSoftwareFragment.java @@ -0,0 +1,88 @@ +package net.oschina.app.improve.search.software; + +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.main.synthesize.detail.ArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.english.detail.EnglishArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.web.WebActivity; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; + +import java.util.List; + +/** + * 软件搜索 + * Created by huanghaibin on 2018/1/5. + */ + +public class SearchSoftwareFragment extends BaseRecyclerFragment + implements SearchSoftwareContract.View { + + static SearchSoftwareFragment newInstance() { + return new SearchSoftwareFragment(); + } + + @Override + protected void onItemClick(Article top, int position) { + if (!TDevice.hasWebView(getContext())) + return; + if (top.getType() == 0) { + if (TypeFormat.isGit(top)) { + WebActivity.show(getContext(), TypeFormat.formatUrl(top)); + } else { + ArticleDetailActivity.show(getContext(), top); + } + } else { + int type = top.getType(); + long id = top.getOscId(); + switch (type) { + case News.TYPE_SOFTWARE: + SoftwareDetailActivity.show(getContext(), id); + break; + case News.TYPE_QUESTION: + QuestionDetailActivity.show(getContext(), id); + break; + case News.TYPE_BLOG: + BlogDetailActivity.show(getContext(), id); + break; + case News.TYPE_TRANSLATE: + NewsDetailActivity.show(getContext(), id, News.TYPE_TRANSLATE); + break; + case News.TYPE_EVENT: + EventDetailActivity.show(getContext(), id); + break; + case News.TYPE_NEWS: + NewsDetailActivity.show(getContext(), id); + break; + case Article.TYPE_ENGLISH: + EnglishArticleDetailActivity.show(mContext, top); + break; + default: + UIHelper.showUrlRedirect(getContext(), top.getUrl()); + break; + } + } + } + + @Override + public void onRefreshSuccess(List

    data) { + if (mAdapter == null || mPresenter == null) + return; + ((SoftwareAdapter) mAdapter).mKeyword = ((SearchSoftwarePresenter) mPresenter).mKeyword; + super.onRefreshSuccess(data); + } + + @Override + protected BaseRecyclerAdapter
    getAdapter() { + return new SoftwareAdapter(mContext); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/search/software/SearchSoftwarePresenter.java b/app/src/main/java/net/oschina/app/improve/search/software/SearchSoftwarePresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..9823d5c2738af0a1728bf6434ae6b3cec9759a5c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/software/SearchSoftwarePresenter.java @@ -0,0 +1,128 @@ +package net.oschina.app.improve.search.software; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.search.v2.SearchBean; + +import java.lang.reflect.Type; + +import cz.msebera.android.httpclient.Header; + +/** + * 搜索软件 + * Created by huanghaibin on 2018/1/5. + */ + +class SearchSoftwarePresenter implements SearchSoftwareContract.Presenter { + private static final int TYPE_SOFTWARE = 1; + private final SearchSoftwareContract.View mView; + private final SearchSoftwareContract.ActionView mActionView; + String mKeyword; + private String mToken; + + SearchSoftwarePresenter(SearchSoftwareContract.View mView, SearchSoftwareContract.ActionView actionView) { + this.mView = mView; + this.mActionView = actionView; + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + if (TextUtils.isEmpty(mKeyword)) { + mActionView.showSearchFailure(R.string.search_keyword_empty_error); + mView.onComplete(); + return; + } + OSChinaApi.searchSoftware(TYPE_SOFTWARE, mKeyword, null, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mActionView.showSearchFailure(R.string.network_timeout_hint); + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean bean = new Gson().fromJson(responseString, getType()); + if (bean != null) { + if (bean.isSuccess() && bean.getResult() != null) { + SearchBean result = bean.getResult(); + mView.onRefreshSuccess(result.getSoftwares()); + mToken = result.getNextPageToken(); + if (result.getSoftwares() == null || + result.getSoftwares().size() < 20) { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + } else { + mView.showNetworkError(R.string.network_timeout_hint); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.onComplete(); + } + } + }); + } + + @Override + public void onLoadMore() { + if (TextUtils.isEmpty(mKeyword)) { + mActionView.showSearchFailure(R.string.search_keyword_empty_error); + mView.onComplete(); + return; + } + OSChinaApi.searchSoftware(TYPE_SOFTWARE, mKeyword, mToken, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mActionView.showSearchFailure(R.string.network_timeout_hint); + mView.showNetworkError(R.string.network_timeout_hint); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean bean = new Gson().fromJson(responseString, getType()); + if (bean != null) { + if (bean.isSuccess() && bean.getResult() != null) { + SearchBean result = bean.getResult(); + mView.onLoadMoreSuccess(result.getSoftwares()); + mToken = result.getNextPageToken(); + if (result.getSoftwares() == null || + result.getSoftwares().size() < 20) { + mView.showNotMore(); + } + } else { + mView.showNotMore(); + } + } else { + mView.showNetworkError(R.string.network_timeout_hint); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.onComplete(); + } + } + }); + } + + private static Type getType() { + return new TypeToken>() { + }.getType(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/search/software/SoftwareAdapter.java b/app/src/main/java/net/oschina/app/improve/search/software/SoftwareAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..7bdb923a19524c52fdea088da77dfe68cefc0f13 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/software/SoftwareAdapter.java @@ -0,0 +1,58 @@ +package net.oschina.app.improve.search.software; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.utils.parser.SearchParser; + +/** + * 只搜索软件 + * Created by huanghaibin on 2018/1/5. + */ + +class SoftwareAdapter extends BaseRecyclerAdapter
    { + String mKeyword; + private RequestManager mLoader; + + SoftwareAdapter(Context context) { + super(context, ONLY_FOOTER); + mLoader = Glide.with(context); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new SoftwareHolder(mInflater.inflate(R.layout.item_list_software, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Article item, int position) { + SoftwareHolder h = (SoftwareHolder) holder; + mLoader.load(item.getSoftwareLogo()) + .fitCenter() + .into(h.mImageLogo); + h.mTextTitle.setText(SearchParser.getInstance().parse(item.getTitle(), mKeyword)); + h.mTextDesc.setText(SearchParser.getInstance().parse(item.getDesc(), mKeyword)); + } + + private static class SoftwareHolder extends RecyclerView.ViewHolder { + private ImageView mImageLogo; + private TextView mTextTitle, mTextDesc; + + private SoftwareHolder(View itemView) { + super(itemView); + mImageLogo = (ImageView) itemView.findViewById(R.id.iv_logo); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_title); + mTextDesc = (TextView) itemView.findViewById(R.id.tv_desc); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/search/v2/SearchActivity.java b/app/src/main/java/net/oschina/app/improve/search/v2/SearchActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..b446119cb7697ce1fe7170ac92f3e484a33d02ac --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/v2/SearchActivity.java @@ -0,0 +1,355 @@ +package net.oschina.app.improve.search.v2; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.SearchView; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.View; +import android.widget.EditText; +import android.widget.RadioGroup; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.main.synthesize.detail.ArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.english.detail.EnglishArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.web.WebActivity; +import net.oschina.app.improve.search.adapters.SearchHistoryAdapter; +import net.oschina.app.improve.search.software.SearchSoftwareActivity; +import net.oschina.app.improve.utils.CacheManager; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.Keyboard; +import net.oschina.app.improve.widget.RecyclerRefreshLayout; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; + +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 新版搜索界面 + * Created by huanghaibin on 2018/1/4. + */ + +public class SearchActivity extends BackActivity implements + SearchContract.View, View.OnClickListener, + BaseRecyclerAdapter.OnItemClickListener { + + private SearchPresenter mPresenter; + @Bind(R.id.view_searcher) + SearchView mViewSearch; + @Bind(R.id.search_src_text) + EditText mViewSearchEditor; + @Bind(R.id.refreshLayout) + RecyclerRefreshLayout mRefreshLayout; + @Bind(R.id.recyclerView) + RecyclerView mRecyclerView; + private SearchAdapter mAdapter; + private SearchHeaderView mHeaderView; + @Bind(R.id.recyclerViewHistory) + RecyclerView mRecyclerViewHistory; + + private SearchHistoryAdapter mSearchAdapter; + private static final String CACHE_NAME = "search_history"; + + public static void show(Context context) { + context.startActivity(new Intent(context, SearchActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_search; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mHeaderView = new SearchHeaderView(this); + mAdapter = new SearchAdapter(this); + mAdapter.setHeaderView(mHeaderView); + mAdapter.setOnItemClickListener(this); + mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + mRecyclerView.setAdapter(mAdapter); + mRefreshLayout.setSuperRefreshLayoutListener(new RecyclerRefreshLayout.SuperRefreshLayoutListener() { + @Override + public void onRefreshing() { + mPresenter.search(SearchPresenter.TYPE_DEFAULT, mViewSearch.getQuery().toString()); + } + + @Override + public void onLoadMore() { + mPresenter.searchMore(SearchPresenter.TYPE_DEFAULT, mViewSearch.getQuery().toString()); + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + } + + @Override + public void onScrollToBottom() { + // TODO: 2018/1/5 + } + }); + + mHeaderView.setOrderChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup radioGroup, int i) { + switch (i) { + case R.id.rb_relate_order: + mPresenter.mOrder = SearchPresenter.ORDER_DEFAULT; + break; + case R.id.rb_hot_order: + mPresenter.mOrder = SearchPresenter.ORDER_HOT; + break; + case R.id.rb_time_order: + mPresenter.mOrder = SearchPresenter.ORDER_TIME; + break; + } + mRefreshLayout.setRefreshing(true); + mPresenter.search(SearchPresenter.TYPE_DEFAULT, mViewSearchEditor.getText().toString()); + } + }); + mHeaderView.setSearchSoftwareListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + SearchSoftwareActivity.show(SearchActivity.this, mPresenter.mKeyword); + } + }); + mViewSearch.setOnCloseListener(new SearchView.OnCloseListener() { + @Override + public boolean onClose() { + // 阻止点击关闭按钮 collapse icon + return true; + } + }); + mViewSearch.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + //mViewSearch.clearFocus(); + TDevice.closeKeyboard(mViewSearchEditor); + mPresenter.search(SearchPresenter.TYPE_DEFAULT, query); + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + if (TextUtils.isEmpty(newText)) { + mRecyclerViewHistory.setVisibility(View.VISIBLE); + } + return false; + } + }); + + mViewSearchEditor.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + + + mSearchAdapter = new SearchHistoryAdapter(this); + mRecyclerViewHistory.setLayoutManager(new LinearLayoutManager(this)); + mRecyclerViewHistory.setAnimation(null); + mRecyclerViewHistory.setAdapter(mSearchAdapter); + List items = CacheManager.readListJson(this, CACHE_NAME, SearchHistoryAdapter.SearchItem.class); + mSearchAdapter.addAll(items); + if (mSearchAdapter.getItems().size() != 0) { + mSearchAdapter.addItem(new SearchHistoryAdapter.SearchItem("清空搜索历史", 1)); + } + mSearchAdapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + SearchHistoryAdapter.SearchItem item = mSearchAdapter.getItem(position); + if (item != null && item.getType() == 0) { + String query = item.getSearchText(); + mViewSearchEditor.setText(query); + mViewSearchEditor.setSelection(query.length()); + mPresenter.search(SearchPresenter.TYPE_DEFAULT, query); + TDevice.closeKeyboard(mViewSearchEditor); + } else { + DialogHelper.getConfirmDialog(SearchActivity.this, "提示", "确认清空搜索历史记录吗?", "确认", "取消", true, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mSearchAdapter.clear(); + } + }).show(); + } + } + }); + mViewSearch.postDelayed(new Runnable() { + @Override + public void run() { + if (isDestroyed() || mViewSearchEditor == null) + return; + Keyboard.openKeyboard(mViewSearchEditor); + } + }, 200); + } + + @Override + protected void initData() { + super.initData(); + mPresenter = new SearchPresenter(this); + } + + @OnClick({R.id.tv_search}) + @Override + public void onClick(View view) { + if (mPresenter == null) + return; + TDevice.closeKeyboard(mViewSearchEditor); + mPresenter.search(SearchPresenter.TYPE_DEFAULT, mViewSearchEditor.getText().toString().trim()); + } + + @Override + public void onItemClick(int position, long itemId) { + Article top = mAdapter.getItem(position); + if (top == null) { + return; + } + if (!TDevice.hasWebView(this)) + return; + if (top.getType() == 0) { + if (TypeFormat.isGit(top)) { + WebActivity.show(this, TypeFormat.formatUrl(top)); + } else { + ArticleDetailActivity.show(this, top); + } + } else { + int type = top.getType(); + long id = top.getOscId(); + switch (type) { + case News.TYPE_SOFTWARE: + SoftwareDetailActivity.show(this, id); + break; + case News.TYPE_QUESTION: + QuestionDetailActivity.show(this, id); + break; + case News.TYPE_BLOG: + BlogDetailActivity.show(this, id); + break; + case News.TYPE_TRANSLATE: + NewsDetailActivity.show(this, id, News.TYPE_TRANSLATE); + break; + case News.TYPE_EVENT: + EventDetailActivity.show(this, id); + break; + case News.TYPE_NEWS: + NewsDetailActivity.show(this, id); + break; + case Article.TYPE_ENGLISH: + EnglishArticleDetailActivity.show(this, top); + break; + default: + UIHelper.showUrlRedirect(this, top.getUrl()); + break; + } + } + } + + @Override + public void showViewStatus(int status) { + if (isDestroyed()) + return; + mRecyclerViewHistory.setVisibility(status); + } + + @Override + public void showAddHistory(String keyword) { + if (isDestroyed()) + return; + SearchHistoryAdapter.SearchItem item = new SearchHistoryAdapter.SearchItem(keyword); + if (mSearchAdapter.getItems().contains(item)) { + mSearchAdapter.removeItem(item); + } + mSearchAdapter.addItem(0, item); + mRecyclerViewHistory.scrollToPosition(0); + SearchHistoryAdapter.SearchItem last = mSearchAdapter.getItem(mSearchAdapter.getItems().size() - 1); + if (last != null && last.getType() == 0) { + mSearchAdapter.addItem(new SearchHistoryAdapter.SearchItem("清空搜索历史", 1)); + } + } + + @Override + public void showNetworkError(int strId) { + if (isDestroyed()) { + return; + } + SimplexToast.show(this, strId); + } + + @Override + public void showSearchSuccess(SearchBean searchBean) { + if (isDestroyed()) + return; + mAdapter.mKeyword = mPresenter.mKeyword; + mHeaderView.mKeyword = mPresenter.mKeyword; + mHeaderView.setData(searchBean); + mAdapter.resetItem(searchBean.getArticles()); + } + + @Override + public void showSearchFailure(int strId) { + if (isDestroyed()) { + return; + } + SimplexToast.show(this, strId); + } + + @Override + public void showNotMore() { + if (isDestroyed()) { + return; + } + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + } + + @Override + public void onComplete() { + if (isDestroyed()) { + return; + } + mRefreshLayout.onComplete(); + } + + @Override + public void showLoadMoreSuccess(List
    articles) { + if (isDestroyed()) { + return; + } + mAdapter.addAll(articles); + } + + @Override + public void showSearchFailure(String str) { + if (isDestroyed()) { + return; + } + SimplexToast.show(this, str); + } + + @Override + public void setPresenter(SearchContract.Presenter presenter) { + // TODO: 2018/1/4 + } + + @Override + protected void onDestroy() { + SearchHistoryAdapter.SearchItem last = mSearchAdapter.getItem(mSearchAdapter.getItems().size() - 1); + if (last != null && last.getType() != 0) { + mSearchAdapter.removeItem(last); + } + CacheManager.saveToJson(this, CACHE_NAME, mSearchAdapter.getItems()); + super.onDestroy(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/search/v2/SearchAdapter.java b/app/src/main/java/net/oschina/app/improve/search/v2/SearchAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..a9aed01db4b09e6edca7b4c823d0eda5c446c3db --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/v2/SearchAdapter.java @@ -0,0 +1,279 @@ +package net.oschina.app.improve.search.v2; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.RecyclerView; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.ImageSpan; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.main.synthesize.DataFormat; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.media.Util; +import net.oschina.app.improve.utils.parser.SearchParser; +import net.oschina.app.util.TDevice; + +/** + * 搜索适配器 + * Created by huanghaibin on 2018/1/5. + */ + +class SearchAdapter extends BaseRecyclerAdapter
    implements BaseRecyclerAdapter.OnLoadingHeaderCallBack { + private static final int VIEW_TYPE_NOT_IMG = 1; + private static final int VIEW_TYPE_ONE_IMG = 2; + private static final int VIEW_TYPE_THREE_IMG = 3; + String mKeyword; + + private static int WIDTH = 0; + private static final String FORMAT = "!/both/330x246/quality/100"; + private RequestManager mLoader; + private OSCApplication.ReadState mReadState; + + + SearchAdapter(Context context) { + super(context, BOTH_HEADER_FOOTER); + mReadState = OSCApplication.getReadState("sub_list"); + setOnLoadingHeaderCallBack(this); + mLoader = Glide.with(mContext); + WIDTH = (Util.getScreenWidth(context) - Util.dipTopx(context, 48)) / 3; + } + + @Override + public int getItemViewType(int position) { + Article article = getItem(position); + if (article != null) { + String imgs[] = article.getImgs(); + if (imgs == null || imgs.length == 0) + return VIEW_TYPE_NOT_IMG; + if (imgs.length < 3) + return VIEW_TYPE_ONE_IMG; + return VIEW_TYPE_THREE_IMG; + } + return super.getItemViewType(position); + } + + @Override + public RecyclerView.ViewHolder onCreateHeaderHolder(ViewGroup parent) { + return new SearchAdapter.HeaderHolder(mHeaderView); + } + + @Override + public void onBindHeaderHolder(RecyclerView.ViewHolder holder, int position) { + + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + if (type == VIEW_TYPE_NOT_IMG) { + return new SearchAdapter.TextHolder(mInflater.inflate(R.layout.item_list_article_not_img, parent, false)); + } else if (type == VIEW_TYPE_ONE_IMG) { + return new SearchAdapter.OneImgHolder(mInflater.inflate(R.layout.item_list_article_one_img, parent, false)); + } else { + return new SearchAdapter.ThreeImgHolder(mInflater.inflate(R.layout.item_list_article_three_img, parent, false)); + } + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Article item, int position) { + int type = getItemViewType(position); + Resources resources = mContext.getResources(); + String sourceName = item.getType() != 0 ? "开源中国" : item.getSource(); + String desc = TextUtils.isEmpty(item.getDesc()) ? "" : item.getDesc().replaceAll("\\s*|\t|\n", ""); + switch (type) { + case VIEW_TYPE_NOT_IMG: + SearchAdapter.TextHolder h = (SearchAdapter.TextHolder) holder; + setTag(h.mTextTitle, h.mImageTag, item); + h.mTextDesc.setText(SearchParser.getInstance().parse(desc, mKeyword)); + h.mTextDesc.setVisibility(TextUtils.isEmpty(desc) ? View.GONE : View.VISIBLE); + h.mTextTime.setText(DataFormat.parsePubDate(item.getPubDate())); + h.mTextAuthor.setText(TextUtils.isEmpty(item.getAuthorName()) ? "匿名" : item.getAuthorName()); + h.mTextOrigin.setText(TextUtils.isEmpty(item.getAuthorName()) ? sourceName : item.getAuthorName()); + h.mTextCommentCount.setText(String.valueOf(item.getCommentCount())); + if (mReadState.already(item.getKey())) { + h.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + h.mTextDesc.setTextColor(TDevice.getColor(resources, R.color.text_secondary_color)); + } else { + h.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_title_color)); + h.mTextDesc.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + } + break; + case VIEW_TYPE_ONE_IMG: + SearchAdapter.OneImgHolder h1 = (SearchAdapter.OneImgHolder) holder; + setTag(h1.mTextTitle, h1.mImageTag, item); + h1.mFrameImage.getLayoutParams().width = WIDTH; + h1.mTextTime.setText(DataFormat.parsePubDate(item.getPubDate())); + h1.mTextAuthor.setText(TextUtils.isEmpty(item.getAuthorName()) ? "匿名" : item.getAuthorName()); + h1.mTextOrigin.setText(TextUtils.isEmpty(item.getAuthorName()) ? sourceName : item.getAuthorName()); + h1.mTextCommentCount.setText(String.valueOf(item.getCommentCount())); + mLoader.load(item.getImgs()[0] + FORMAT) + .fitCenter() + .error(R.mipmap.ic_split_graph) + .into(h1.mImageView); + if (mReadState.already(item.getKey())) { + h1.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + } else { + h1.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_title_color)); + } + break; + case VIEW_TYPE_THREE_IMG: + SearchAdapter.ThreeImgHolder h2 = (SearchAdapter.ThreeImgHolder) holder; + setTag(h2.mTextTitle, h2.mImageTag, item); + h2.mTextTime.setText(DataFormat.parsePubDate(item.getPubDate())); + h2.mTextAuthor.setText(TextUtils.isEmpty(item.getAuthorName()) ? "匿名" : item.getAuthorName()); + h2.mTextOrigin.setText(TextUtils.isEmpty(item.getAuthorName()) ? sourceName : item.getAuthorName()); + h2.mTextCommentCount.setText(String.valueOf(item.getCommentCount())); + mLoader.load(item.getImgs()[0] + FORMAT) + .fitCenter() + .error(R.mipmap.ic_split_graph) + .into(h2.mImageOne); + mLoader.load(item.getImgs()[1] + FORMAT) + .fitCenter() + .into(h2.mImageTwo); + mLoader.load(item.getImgs()[2] + FORMAT) + .fitCenter() + .error(R.mipmap.ic_split_graph) + .into(h2.mImageThree); + if (mReadState.already(item.getKey())) { + h2.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_desc_color)); + } else { + h2.mTextTitle.setTextColor(TDevice.getColor(resources, R.color.text_title_color)); + } + break; + } + + } + + + private void setTag(TextView textView, ImageView imageView, Article article) { + if (article.getType() == News.TYPE_QUESTION) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_question); + imageView.setVisibility(View.VISIBLE); + } else if (TypeFormat.isGit(article)) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_gitee); + imageView.setVisibility(View.VISIBLE); + } else if (TypeFormat.isZB(article)) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_zb); + imageView.setVisibility(View.VISIBLE); + } else if (article.getType() == News.TYPE_SOFTWARE) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_software); + imageView.setVisibility(View.VISIBLE); + } else if (article.getType() == Article.TYPE_AD) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_ad); + imageView.setVisibility(View.VISIBLE); + } else if (article.getType() == News.TYPE_TRANSLATE) { + setEmptyTag(textView, article); + imageView.setImageResource(R.mipmap.tag_translate); + imageView.setVisibility(View.VISIBLE); + } else { + textView.setText(SearchParser.getInstance().parse(article.getTitle(), mKeyword)); + imageView.setVisibility(View.GONE); + } + } + + private void setEmptyTag(TextView textView, Article article) { + SpannableStringBuilder spannable = new SpannableStringBuilder(); + spannable.append("[icon] "); + spannable.append(SearchParser.getInstance().parse(article.getTitle(), mKeyword)); + Drawable img = mContext.getResources().getDrawable(R.mipmap.tag_empty); + if (img != null) { + img.setBounds(0, 0, img.getIntrinsicWidth(), img.getIntrinsicHeight()); + } + ImageSpan imageSpan = new ImageSpan(img, ImageSpan.ALIGN_BOTTOM); + spannable.setSpan(imageSpan, 0, 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + + textView.setText(spannable); + } + + + private static final class TextHolder extends RecyclerView.ViewHolder { + TextView mTextTitle, + mTextDesc, + mTextTime, + mTextOrigin, + mTextAuthor, + mTextCommentCount; + ImageView mImageTag; + + TextHolder(View itemView) { + super(itemView); + mImageTag = (ImageView) itemView.findViewById(R.id.iv_tag); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_title); + mTextDesc = (TextView) itemView.findViewById(R.id.tv_desc); + mTextTime = (TextView) itemView.findViewById(R.id.tv_time); + mTextOrigin = (TextView) itemView.findViewById(R.id.tv_origin); + mTextAuthor = (TextView) itemView.findViewById(R.id.tv_author); + mTextCommentCount = (TextView) itemView.findViewById(R.id.tv_comment_count); + } + } + + private static final class OneImgHolder extends RecyclerView.ViewHolder { + TextView mTextTitle, + mTextTime, + mTextOrigin, + mTextAuthor, + mTextCommentCount; + ImageView mImageView, mImageTag; + FrameLayout mFrameImage; + + OneImgHolder(View itemView) { + super(itemView); + mFrameImage = (FrameLayout) itemView.findViewById(R.id.fl_image); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_title); + mTextTime = (TextView) itemView.findViewById(R.id.tv_time); + mImageView = (ImageView) itemView.findViewById(R.id.iv_image); + mTextOrigin = (TextView) itemView.findViewById(R.id.tv_origin); + mTextAuthor = (TextView) itemView.findViewById(R.id.tv_author); + mImageTag = (ImageView) itemView.findViewById(R.id.iv_tag); + mTextCommentCount = (TextView) itemView.findViewById(R.id.tv_comment_count); + } + } + + private static final class ThreeImgHolder extends RecyclerView.ViewHolder { + TextView mTextTitle, + mTextTime, + mTextOrigin, + mTextAuthor, + mTextCommentCount; + ImageView mImageOne, mImageTwo, mImageThree, mImageTag; + + ThreeImgHolder(View itemView) { + super(itemView); + mImageTag = (ImageView) itemView.findViewById(R.id.iv_tag); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_title); + mTextTime = (TextView) itemView.findViewById(R.id.tv_time); + mImageOne = (ImageView) itemView.findViewById(R.id.iv_img_1); + mImageTwo = (ImageView) itemView.findViewById(R.id.iv_img_2); + mImageThree = (ImageView) itemView.findViewById(R.id.iv_img_3); + mTextOrigin = (TextView) itemView.findViewById(R.id.tv_origin); + mTextAuthor = (TextView) itemView.findViewById(R.id.tv_author); + mTextCommentCount = (TextView) itemView.findViewById(R.id.tv_comment_count); + } + } + + + private static final class HeaderHolder extends RecyclerView.ViewHolder { + HeaderHolder(View itemView) { + super(itemView); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/search/v2/SearchBean.java b/app/src/main/java/net/oschina/app/improve/search/v2/SearchBean.java new file mode 100644 index 0000000000000000000000000000000000000000..8381cdaf79ba7e9dae9c9da25e3ce0864fc425ff --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/v2/SearchBean.java @@ -0,0 +1,87 @@ +package net.oschina.app.improve.search.v2; + +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.simple.Author; + +import java.io.Serializable; +import java.util.List; + +/** + * 搜索界面 + * Created by huanghaibin on 2018/1/4. + */ +@SuppressWarnings("unused") +public class SearchBean implements Serializable { + private int authorsCount; + private int softwareCount; + private int articleCount; + private String nextPageToken; + private String prevPageToken; + private List authors; + private List
    softwares; + private List
    articles; + + public int getAuthorsCount() { + return authorsCount; + } + + public void setAuthorsCount(int authorsCount) { + this.authorsCount = authorsCount; + } + + public int getSoftwareCount() { + return softwareCount; + } + + public void setSoftwareCount(int softwareCount) { + this.softwareCount = softwareCount; + } + + public int getArticleCount() { + return articleCount; + } + + public void setArticleCount(int articleCount) { + this.articleCount = articleCount; + } + + public List getAuthors() { + return authors; + } + + public void setAuthors(List authors) { + this.authors = authors; + } + + public List
    getSoftwares() { + return softwares; + } + + public void setSoftwares(List
    softwares) { + this.softwares = softwares; + } + + public List
    getArticles() { + return articles; + } + + public void setArticles(List
    articles) { + this.articles = articles; + } + + public String getNextPageToken() { + return nextPageToken; + } + + public void setNextPageToken(String nextPageToken) { + this.nextPageToken = nextPageToken; + } + + public String getPrevPageToken() { + return prevPageToken; + } + + public void setPrevPageToken(String prevPageToken) { + this.prevPageToken = prevPageToken; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/search/v2/SearchContract.java b/app/src/main/java/net/oschina/app/improve/search/v2/SearchContract.java new file mode 100644 index 0000000000000000000000000000000000000000..b767eaebaf0d36d62ae91853704355471dc0db96 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/v2/SearchContract.java @@ -0,0 +1,39 @@ +package net.oschina.app.improve.search.v2; + +import net.oschina.app.improve.base.BasePresenter; +import net.oschina.app.improve.base.BaseView; +import net.oschina.app.improve.bean.Article; + +import java.util.List; + +/** + * 新版搜索界面 + * Created by huanghaibin on 2018/1/4. + */ +interface SearchContract { + + interface View extends BaseView { + void showSearchSuccess(SearchBean searchBean); + + void showNotMore(); + + void showLoadMoreSuccess(List
    articles); + + void onComplete(); + + void showSearchFailure(int strId); + + void showSearchFailure(String str); + + void showViewStatus(int status); + + void showAddHistory(String keyword); + } + + interface Presenter extends BasePresenter { + void search(int type, String keyword); + + + void searchMore(int type,String keyword); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/search/v2/SearchHeaderView.java b/app/src/main/java/net/oschina/app/improve/search/v2/SearchHeaderView.java new file mode 100644 index 0000000000000000000000000000000000000000..2b10d168e362163f2026ed3fa07985c9e4d16bb2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/v2/SearchHeaderView.java @@ -0,0 +1,186 @@ +package net.oschina.app.improve.search.v2; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RadioGroup; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Article; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.main.synthesize.detail.ArticleDetailActivity; +import net.oschina.app.improve.main.synthesize.web.WebActivity; +import net.oschina.app.improve.media.Util; +import net.oschina.app.improve.utils.parser.SearchParser; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; + +/** + * 搜索头部 + * Created by huanghaibin on 2018/1/5. + */ + +public class SearchHeaderView extends LinearLayout implements BaseRecyclerAdapter.OnItemClickListener{ + + String mKeyword; + private Adapter mAdapter; + private RadioGroup mRadioGroup; + private TextView mTextSoftwareCount; + private LinearLayout mLinearSoftware; + private RecyclerView mRecyclerView; + private LinearLayout mLinearArticles; + + public SearchHeaderView(Context context) { + this(context, null); + } + + public SearchHeaderView(final Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + LayoutInflater.from(context).inflate(R.layout.layout_search_header, this, true); + mRecyclerView = (RecyclerView) findViewById(R.id.recyclerSoftware); + mRecyclerView.setLayoutManager(new LinearLayoutManager(context)); + mAdapter = new Adapter(context); + mRecyclerView.setAdapter(mAdapter); + mAdapter.setOnItemClickListener(this); + mLinearSoftware = (LinearLayout) findViewById(R.id.ll_software); + mLinearArticles = (LinearLayout) findViewById(R.id.ll_article); + mRadioGroup = (RadioGroup) findViewById(R.id.radioGroup); + mTextSoftwareCount = (TextView) findViewById(R.id.tv_software_count); + setVisibility(GONE); + } + + void setSearchSoftwareListener(OnClickListener listener) { + mTextSoftwareCount.setOnClickListener(listener); + } + + void setData(SearchBean bean) { + if (bean.getArticleCount() == 0) { + mLinearArticles.setVisibility(GONE); + } else { + setVisibility(VISIBLE); + mLinearArticles.setVisibility(VISIBLE); + } + if (bean.getSoftwares() == null) + return; + if (bean.getSoftwares().size() == 0) { + mLinearSoftware.setVisibility(GONE); + mRecyclerView.setVisibility(GONE); + } else { + setVisibility(VISIBLE); + mLinearSoftware.setVisibility(VISIBLE); + mRecyclerView.setVisibility(VISIBLE); + mLinearArticles.setVisibility(VISIBLE); + } + mAdapter.mKeyword = mKeyword; + mAdapter.resetItem(bean.getSoftwares()); + mTextSoftwareCount.setText(String.format("查看其余%s款软件",bean.getSoftwareCount())); + } + + void setOrderChangeListener(RadioGroup.OnCheckedChangeListener listener) { + mRadioGroup.setOnCheckedChangeListener(listener); + } + + @Override + public void onItemClick(int position, long itemId) { + Article top = mAdapter.getItem(position); + if(top == null){ + return; + } + if (!TDevice.hasWebView(getContext())) + return; + if (top.getType() == 0) { + if (TypeFormat.isGit(top)) { + WebActivity.show(getContext(), TypeFormat.formatUrl(top)); + } else { + ArticleDetailActivity.show(getContext(), top); + } + } else { + int type = top.getType(); + long id = top.getOscId(); + switch (type) { + case News.TYPE_SOFTWARE: + SoftwareDetailActivity.show(getContext(), id); + break; + case News.TYPE_QUESTION: + QuestionDetailActivity.show(getContext(), id); + break; + case News.TYPE_BLOG: + BlogDetailActivity.show(getContext(), id); + break; + case News.TYPE_TRANSLATE: + NewsDetailActivity.show(getContext(), id, News.TYPE_TRANSLATE); + break; + case News.TYPE_EVENT: + EventDetailActivity.show(getContext(), id); + break; + case News.TYPE_NEWS: + NewsDetailActivity.show(getContext(), id); + break; + default: + UIHelper.showUrlRedirect(getContext(), top.getUrl()); + break; + } + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + widthMeasureSpec = MeasureSpec.makeMeasureSpec(Util.getScreenWidth(getContext()), MeasureSpec.EXACTLY); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + private static class Adapter extends BaseRecyclerAdapter
    { + private RequestManager mLoader; + private String mKeyword; + + public Adapter(Context context) { + super(context, NEITHER); + mLoader = Glide.with(context); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new SoftwareHolder(mInflater.inflate(R.layout.item_list_software, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Article item, int position) { + SoftwareHolder h = (SoftwareHolder) holder; + mLoader.load(item.getSoftwareLogo()) + .fitCenter() + .into(h.mImageLogo); + h.mTextTitle.setText(SearchParser.getInstance().parse(item.getTitle(),mKeyword)); + h.mTextDesc.setText(SearchParser.getInstance().parse(item.getDesc(),mKeyword)); + } + + private static class SoftwareHolder extends RecyclerView.ViewHolder { + private ImageView mImageLogo; + private TextView mTextTitle, mTextDesc; + + private SoftwareHolder(View itemView) { + super(itemView); + mImageLogo = (ImageView) itemView.findViewById(R.id.iv_logo); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_title); + mTextDesc = (TextView) itemView.findViewById(R.id.tv_desc); + } + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/search/v2/SearchPresenter.java b/app/src/main/java/net/oschina/app/improve/search/v2/SearchPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..6521f4c9244255af277ad61d389381f7bb817e55 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/search/v2/SearchPresenter.java @@ -0,0 +1,134 @@ +package net.oschina.app.improve.search.v2; + +import android.text.TextUtils; +import android.view.View; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.base.ResultBean; + +import java.lang.reflect.Type; + +import cz.msebera.android.httpclient.Header; + +/** + * 新版搜索界面 + * Created by huanghaibin on 2018/1/4. + */ + +class SearchPresenter implements SearchContract.Presenter { + static final int TYPE_DEFAULT = -1; + static final int ORDER_DEFAULT = 1; + static final int ORDER_HOT = 2; + static final int ORDER_TIME = 3; + private final SearchContract.View mView; + private String mToken; + int mOrder; + String mKeyword; + + SearchPresenter(SearchContract.View mView) { + this.mView = mView; + this.mView.setPresenter(this); + mOrder = ORDER_DEFAULT; + } + + @Override + public void search(int type, String keyword) { + if (TextUtils.isEmpty(keyword)) { + mView.showSearchFailure(R.string.search_keyword_empty_error); + mView.showViewStatus(View.VISIBLE); + mView.onComplete(); + return; + } + mView.showViewStatus(View.GONE); + mView.showAddHistory(keyword); + mKeyword = keyword; + OSChinaApi.search(type, mOrder, keyword, null, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showSearchFailure(R.string.network_timeout_hint); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean bean = new Gson().fromJson(responseString, getType()); + if (bean != null) { + if (bean.isSuccess() && bean.getResult() != null) { + SearchBean result = bean.getResult(); + mView.showSearchSuccess(result); + mToken = result.getNextPageToken(); + if ((result.getArticles() == null || + result.getArticles().size() == 0) && + (result.getSoftwares() == null || result.getSoftwares().size() == 0)) { + mView.showNotMore(); + } + } else { + mView.showSearchFailure(bean.getMessage()); + } + } else { + mView.showSearchFailure(R.string.search_error); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.onComplete(); + } + } + }); + } + + + @Override + public void searchMore(int type, String keyword) { + if (TextUtils.isEmpty(mKeyword)) { + mView.showSearchFailure(R.string.search_keyword_empty_error); + return; + } + OSChinaApi.search(type, mOrder, mKeyword, mToken, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showSearchFailure(R.string.network_timeout_hint); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean bean = new Gson().fromJson(responseString, getType()); + if (bean != null) { + if (bean.isSuccess() && bean.getResult() != null) { + SearchBean result = bean.getResult(); + mToken = result.getNextPageToken(); + mView.showLoadMoreSuccess(result.getArticles()); + if (result.getArticles() == null || + result.getArticles().size() == 0) { + mView.showNotMore(); + } + } else { + mView.showSearchFailure(bean.getMessage()); + } + } else { + mView.showSearchFailure(R.string.search_error); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.onComplete(); + } + } + }); + } + + private static Type getType() { + return new TypeToken>() { + }.getType(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/setting/AboutActivity.java b/app/src/main/java/net/oschina/app/improve/setting/AboutActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..832cc5b8fbf3bfa914327c67ae61cf70a1489101 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/setting/AboutActivity.java @@ -0,0 +1,33 @@ +package net.oschina.app.improve.setting; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.R; +import net.oschina.app.fragment.AboutOSCFragment; +import net.oschina.app.improve.base.activities.BackActivity; + +/** + * 关于界面 + * Created by huanghaibin on 2017/10/28. + */ + +public class AboutActivity extends BackActivity { + + public static void show(Context context) { + context.startActivity(new Intent(context, AboutActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_setting; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + addFragment(R.id.fl_content, new AboutOSCFragment()); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/setting/SettingActivity.java b/app/src/main/java/net/oschina/app/improve/setting/SettingActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..394984febeaa0113e7ea4e0342ef50fe1c5747c8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/setting/SettingActivity.java @@ -0,0 +1,32 @@ +package net.oschina.app.improve.setting; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; + +/** + * 设置界面 + * Created by huanghaibin on 2017/10/27. + */ + +public class SettingActivity extends BackActivity { + + public static void show(Context context) { + context.startActivity(new Intent(context, SettingActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_setting; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + addFragment(R.id.fl_content, SettingFragment.newInstance()); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/setting/SettingFragment.java b/app/src/main/java/net/oschina/app/improve/setting/SettingFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..0b42b6e03a1972a0547a9b0f454b6f5d69f58bb8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/setting/SettingFragment.java @@ -0,0 +1,265 @@ +package net.oschina.app.improve.setting; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.DialogInterface; +import android.content.Intent; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import net.oschina.app.AppConfig; +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.bean.Version; +import net.oschina.app.improve.main.FeedBackActivity; +import net.oschina.app.improve.main.update.CheckUpdateManager; +import net.oschina.app.improve.main.update.DownloadService; +import net.oschina.app.improve.main.update.OSCSharedPreference; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.SystemConfigView; +import net.oschina.app.improve.widget.togglebutton.ToggleButton; +import net.oschina.app.util.FileUtil; +import net.oschina.app.util.MethodsCompat; +import net.oschina.app.util.UIHelper; + +import java.io.File; +import java.util.List; + +import butterknife.Bind; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * 设置界面 + * Created by huanghaibin on 2017/10/27. + */ + +public class SettingFragment extends BaseFragment implements + View.OnClickListener, + EasyPermissions.PermissionCallbacks, + CheckUpdateManager.RequestPermissions { + + private static final int RC_EXTERNAL_STORAGE = 0x04;//存储权限 + + @Bind(R.id.tv_cache_size) + TextView mTvCacheSize; + @Bind(R.id.rl_check_version) + FrameLayout mRlCheck_version; + @Bind(R.id.tb_double_click_exit) + ToggleButton mTbDoubleClickExit; + @Bind(R.id.tb_clip) + ToggleButton mTbClip; + @Bind(R.id.setting_line_top) + View mSettingLineTop; + @Bind(R.id.setting_line_bottom) + View mSettingLineBottom; + @Bind(R.id.rl_cancel) + FrameLayout mCancel; + + private Version mVersion; + + public static SettingFragment newInstance() { + return new SettingFragment(); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_setting; + } + + @Override + protected void initWidget(View view) { + super.initWidget(view); + + mTbDoubleClickExit.setOnToggleChanged(new ToggleButton.OnToggleChanged() { + @Override + public void onToggle(boolean on) { + AppContext.set(AppConfig.KEY_DOUBLE_CLICK_EXIT, on); + } + }); + + mTbClip.setOnToggleChanged(new ToggleButton.OnToggleChanged() { + @Override + public void onToggle(boolean on) { + OSCSharedPreference.getInstance().putRelateClip(on); + } + }); + + view.findViewById(R.id.rl_clean_cache).setOnClickListener(this); + view.findViewById(R.id.rl_double_click_exit).setOnClickListener(this); + view.findViewById(R.id.rl_clip).setOnClickListener(this); + view.findViewById(R.id.rl_about).setOnClickListener(this); + view.findViewById(R.id.rl_check_version).setOnClickListener(this); + // view.findViewById(R.id.rl_exit).setOnClickListener(this); + view.findViewById(R.id.rl_feedback).setOnClickListener(this); + mCancel.setOnClickListener(this); + + SystemConfigView.show((ViewGroup) view.findViewById(R.id.lay_linear)); + } + + @Override + protected void initData() { + super.initData(); + if (AppContext.get(AppConfig.KEY_DOUBLE_CLICK_EXIT, true)) { + mTbDoubleClickExit.setToggleOn(); + } else { + mTbDoubleClickExit.setToggleOff(); + } + if(OSCSharedPreference.getInstance().isRelateClip()){ + mTbClip.setToggleOn(); + }else { + mTbClip.setToggleOff(); + } + calculateCacheSize(); + } + + @Override + public void onResume() { + super.onResume(); + boolean login = AccountHelper.isLogin(); + if (!login) { + mCancel.setVisibility(View.INVISIBLE); + mSettingLineTop.setVisibility(View.INVISIBLE); + mSettingLineBottom.setVisibility(View.INVISIBLE); + } else { + mCancel.setVisibility(View.VISIBLE); + mSettingLineTop.setVisibility(View.VISIBLE); + mSettingLineBottom.setVisibility(View.VISIBLE); + } + } + + /** + * 计算缓存的大小 + */ + private void calculateCacheSize() { + long fileSize = 0; + String cacheSize = "0KB"; + File filesDir = getActivity().getFilesDir(); + File cacheDir = getActivity().getCacheDir(); + + fileSize += FileUtil.getDirSize(filesDir); + fileSize += FileUtil.getDirSize(cacheDir); + // 2.2版本才有将应用缓存转移到sd卡的功能 + if (AppContext.isMethodsCompat(android.os.Build.VERSION_CODES.FROYO)) { + File externalCacheDir = MethodsCompat + .getExternalCacheDir(getActivity()); + fileSize += FileUtil.getDirSize(externalCacheDir); + } + if (fileSize > 0) + cacheSize = FileUtil.formatFileSize(fileSize); + mTvCacheSize.setText(cacheSize); + } + + @Override + public void onClick(View v) { + final int id = v.getId(); + switch (id) { + case R.id.rl_clean_cache: + onClickCleanCache(); + break; + case R.id.rl_double_click_exit: + mTbDoubleClickExit.toggle(); + break; + case R.id.rl_clip: + mTbClip.toggle(); + break; + case R.id.rl_feedback: + //UIHelper.showSimpleBack(getActivity(), SimpleBackPage.FEED_BACK); + if (!AccountHelper.isLogin()) { + LoginActivity.show(getContext()); + return; + } + FeedBackActivity.show(getActivity()); + break; + case R.id.rl_about: + AboutActivity.show(mContext); + break; + case R.id.rl_check_version: + onClickUpdate(); + break; + case R.id.rl_cancel: + // 清理所有缓存 + UIHelper.clearAppCache(false); + // 注销操作 + AccountHelper.logout(mCancel, new Runnable() { + @SuppressLint("SetTextI18n") + @Override + public void run() { + //getActivity().finish(); + mTvCacheSize.setText("0KB"); + AppContext.showToastShort(getString(R.string.logout_success_hint)); + mCancel.setVisibility(View.INVISIBLE); + mSettingLineTop.setVisibility(View.INVISIBLE); + mSettingLineBottom.setVisibility(View.INVISIBLE); + } + }); + break; + default: + break; + } + + } + + private void onClickUpdate() { + CheckUpdateManager manager = new CheckUpdateManager(getActivity(), true); + manager.setCaller(this); + manager.checkUpdate(true); + } + + private void onClickCleanCache() { + DialogHelper.getConfirmDialog(getActivity(), "是否清空缓存?", new DialogInterface.OnClickListener + () { + @SuppressLint("SetTextI18n") + @Override + public void onClick(DialogInterface dialogInterface, int i) { + UIHelper.clearAppCache(true); + mTvCacheSize.setText("0KB"); + } + }).show(); + } + + @Override + public void call(Version version) { + this.mVersion = version; + requestExternalStorage(); + } + + @SuppressLint("InlinedApi") + @AfterPermissionGranted(RC_EXTERNAL_STORAGE) + public void requestExternalStorage() { + if (EasyPermissions.hasPermissions(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)) { + DownloadService.startService(getActivity(), mVersion.getDownloadUrl()); + } else { + EasyPermissions.requestPermissions(this, "", RC_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE); + } + } + + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + DialogHelper.getConfirmDialog(getActivity(), "温馨提示", "需要开启开源中国对您手机的存储权限才能下载安装,是否现在开启", "去开启", "取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + } + }).show(); + } + + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/share/BaseShare.java b/app/src/main/java/net/oschina/app/improve/share/BaseShare.java new file mode 100644 index 0000000000000000000000000000000000000000..e637890258a82cd2cb98f9f790d6f3c5eef62cdb --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/share/BaseShare.java @@ -0,0 +1,127 @@ +package net.oschina.app.improve.share; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.os.Environment; + +import net.oschina.common.utils.StreamUtil; + +import java.io.File; +import java.io.FileOutputStream; + +/** + * base share + * Created by huanghaibin on 2017/6/12. + */ +@SuppressWarnings("unused") +public abstract class BaseShare { + static final String APP_PROVIDER = "net.oschina.app.provider"; + Builder mBuilder; + + BaseShare(Builder mBuilder) { + this.mBuilder = mBuilder; + } + + public abstract boolean share(); + + public abstract void shareImage(); + + public static final class Builder { + Activity mActivity; + String title; + String content; + int resId; + String url; + String imageUrl; + Bitmap bitmap; + Bitmap thumbBitmap; + boolean isShareApp; + boolean isShareImage; + int itemIcon;//显示的分享项图标 + String itemTitle;//显示的分享项名称 + + public Builder(Activity mActivity) { + this.mActivity = mActivity; + this.isShareImage = true; + } + + public Builder resId(int resId) { + this.resId = resId; + return this; + } + + public Builder itemIcon(int itemIcon) { + this.itemIcon = itemIcon; + return this; + } + + public Builder itemTitle(String itemTitle) { + this.itemTitle = itemTitle; + return this; + } + + public Builder title(String title) { + this.title = title; + return this; + } + + public Builder url(String url) { + this.url = url; + return this; + } + + public Builder imageUrl(String imageUrl) { + this.imageUrl = imageUrl; + return this; + } + + public Builder content(String content) { + this.content = content; + return this; + } + + public Builder bitmap(Bitmap bitmap) { + this.bitmap = bitmap; + return this; + } + + public Builder thumbBitmap(Bitmap thumbBitmap) { + this.thumbBitmap = thumbBitmap; + return this; + } + + @SuppressWarnings("all") + public Builder isShareApp(boolean isShareApp) { + this.isShareApp = isShareApp; + return this; + } + + public Builder isShareImage(boolean isShareImage) { + this.isShareImage = isShareImage; + return this; + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + static String saveImage(Bitmap bitmap) { + FileOutputStream os = null; + String url = null; + try { + File file = new File(url = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + .getAbsolutePath() + File.separator + "开源中国/share/"); + if (!file.exists()) { + file.mkdirs(); + } + url = file.getPath() + String.valueOf(System.currentTimeMillis()) + ".jpg"; + os = new FileOutputStream(url); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + os.flush(); + os.close(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + StreamUtil.close(os); + } + return url; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/share/MomentsShare.java b/app/src/main/java/net/oschina/app/improve/share/MomentsShare.java new file mode 100644 index 0000000000000000000000000000000000000000..b9a0b1a642e46cd4d8680a04c901a9a34bdc7946 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/share/MomentsShare.java @@ -0,0 +1,49 @@ +package net.oschina.app.improve.share; + +import android.content.Intent; +import android.net.Uri; +import android.support.v4.content.FileProvider; +import android.text.TextUtils; + +import java.io.File; + +/** + * WeChatShare + * Created by huanghaibin on 2017/6/12. + */ +@SuppressWarnings("unused") +public class MomentsShare extends WeChatShare { + private static final String APP_ID = ""; + + MomentsShare(Builder mBuilder) { + super(mBuilder); + } + + @Override + public boolean share() { + if (mBuilder.isShareImage && mBuilder.bitmap != null) { + shareImage(); + } else { + wechatShare(1); + } + return true; + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Override + public void shareImage() { + try { + String url = saveImage(mBuilder.bitmap); + if (TextUtils.isEmpty(url)) return; + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + Uri uri = FileProvider.getUriForFile(mBuilder.mActivity, APP_PROVIDER, new File(url)); + intent.putExtra(Intent.EXTRA_STREAM, uri);//uri为你要分享的图片的uri + intent.setType("image/*"); + intent.setClassName("com.tencent.mm", "com.tencent.mm.ui.tools.ShareToTimeLineUI"); + mBuilder.mActivity.startActivityForResult(intent, 1); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/share/QZoneShare.java b/app/src/main/java/net/oschina/app/improve/share/QZoneShare.java new file mode 100644 index 0000000000000000000000000000000000000000..205e5a2b68d8b5a800b1565fc2bf9f34c944ce70 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/share/QZoneShare.java @@ -0,0 +1,81 @@ +package net.oschina.app.improve.share; + +import android.os.Bundle; +import android.text.TextUtils; + +import com.tencent.connect.share.QQShare; +import com.tencent.tauth.IUiListener; +import com.tencent.tauth.Tencent; +import com.tencent.tauth.UiError; + +import net.oschina.app.R; +import net.oschina.app.improve.widget.SimplexToast; + +/** + * QZoneShare + * Created by huanghaibin on 2017/6/12. + */ + +public class QZoneShare extends BaseShare implements IUiListener { + private static final String APP_ID = "1101982202"; + private Tencent tencent; + + QZoneShare(Builder mBuilder) { + super(mBuilder); + tencent = Tencent.createInstance(APP_ID, mBuilder.mActivity.getApplicationContext()); + } + + @Override + public boolean share() { + if (mBuilder.isShareImage && mBuilder.bitmap != null) { + shareImage(); + } else { + Bundle bundle = initShare(); + bundle.putInt(QQShare.SHARE_TO_QQ_EXT_INT, QQShare.SHARE_TO_QQ_FLAG_QZONE_AUTO_OPEN); + tencent.shareToQQ(mBuilder.mActivity, bundle, this); + } + return true; + } + + @Override + public void shareImage() { + String url = saveImage(mBuilder.bitmap); + if (TextUtils.isEmpty(url)) return; + Bundle params = new Bundle(); + params.putString(QQShare.SHARE_TO_QQ_IMAGE_LOCAL_URL, url); + params.putString(QQShare.SHARE_TO_QQ_APP_NAME, mBuilder.title); + params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_IMAGE); + params.putInt(QQShare.SHARE_TO_QQ_EXT_INT, QQShare.SHARE_TO_QQ_FLAG_QZONE_AUTO_OPEN); + tencent.shareToQQ(mBuilder.mActivity, params, this); + } + + private Bundle initShare() { + Bundle params = new Bundle(); + params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_DEFAULT); + params.putString(QQShare.SHARE_TO_QQ_TITLE, mBuilder.title); + params.putString(QQShare.SHARE_TO_QQ_SUMMARY, mBuilder.content); + params.putString(QQShare.SHARE_TO_QQ_TARGET_URL, mBuilder.url); + params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL, mBuilder.imageUrl); + params.putInt(QQShare.SHARE_TO_QQ_IMAGE_LOCAL_URL, R.mipmap.ic_share_app_logo); + if (mBuilder.isShareApp) { + params.putString(QQShare.SHARE_TO_QQ_APP_NAME, mBuilder.title); + params.putInt(QQShare.SHARE_TO_QQ_IMAGE_LOCAL_URL, R.mipmap.ic_share_app_logo); + } + return params; + } + + @Override + public void onComplete(Object o) { + SimplexToast.show(mBuilder.mActivity, "成功分享"); + } + + @Override + public void onError(UiError uiError) { + SimplexToast.show(mBuilder.mActivity, "分享失败"); + } + + @Override + public void onCancel() { + SimplexToast.show(mBuilder.mActivity, "分享取消"); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/share/ShareDialog.java b/app/src/main/java/net/oschina/app/improve/share/ShareDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..da1aaebd62301233ba54f6ed520b08998b227d54 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/share/ShareDialog.java @@ -0,0 +1,159 @@ +package net.oschina.app.improve.share; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.view.View; + +import net.oschina.app.R; +import net.oschina.app.improve.widget.BottomDialog; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; +import net.oschina.common.utils.StreamUtil; + +import java.io.File; +import java.io.FileOutputStream; + +/** + * 分享对话框 + * Created by huanghaibin on 2017/6/25. + */ +@SuppressWarnings("unused") +public class ShareDialog extends BottomDialog implements View.OnClickListener, DialogInterface.OnDismissListener { + private BaseShare.Builder mBuilder; + public static final int ONE_DAY = 86400000; + + @SuppressLint("InflateParams") + public ShareDialog(@NonNull Context context) { + super(context, true); + View view = getLayoutInflater().inflate(R.layout.dialog_share, null); + view.findViewById(R.id.ll_share_wechat).setOnClickListener(this); + view.findViewById(R.id.ll_share_moments).setOnClickListener(this); + view.findViewById(R.id.ll_share_weibo).setOnClickListener(this); + view.findViewById(R.id.ll_share_qq).setOnClickListener(this); + view.findViewById(R.id.ll_share_browser).setOnClickListener(this); + view.findViewById(R.id.ll_share_copy).setOnClickListener(this); + setContentView(view); + setOnDismissListener(this); + } + + + public void init(Activity activity, String title, String content, String url) { + if (mBuilder == null) { + mBuilder = new BaseShare.Builder(activity); + } + mBuilder.title(title) + .content(TextUtils.isEmpty(content) ? title : content.length() > 30 ? content.substring(0, 30) : content) + .url(url); + } + + @SuppressWarnings("all") + public void setShareApp(boolean isShareApp) { + if (mBuilder != null) { + mBuilder.isShareApp(true); + } + } + + public void setBitmap(Bitmap bitmap) { + if (mBuilder != null) { + mBuilder.bitmap(bitmap); + } + clearShare(); + } + + public void setThumbBitmap(Bitmap thumbBitmap) { + if (mBuilder != null) { + mBuilder.thumbBitmap(thumbBitmap); + } + clearShare(); + } + + @Override + public void onClick(View v) { + if (mBuilder == null) + return; + BaseShare share = null; + switch (v.getId()) { + case R.id.ll_share_wechat: + share = new WeChatShare(mBuilder); + break; + case R.id.ll_share_moments: + share = new MomentsShare(mBuilder); + break; + case R.id.ll_share_weibo: + share = new SinaShare(mBuilder); + break; + case R.id.ll_share_qq: + share = new TencentQQShare(mBuilder); + break; + case R.id.ll_share_browser: + UIHelper.openExternalBrowser(getContext(), mBuilder.url); + break; + case R.id.ll_share_copy: + TDevice.copyTextToBoard(mBuilder.url); + break; + } + if (share != null) + share.share(); + dismiss(); + } + + + /** + * 保存分享 + */ + @SuppressWarnings("ResultOfMethodCallIgnored,unused") + private static boolean saveShare(Bitmap bitmap, Context context) { + FileOutputStream os = null; + try { + String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + .getAbsolutePath() + File.separator + "开源中国/share/"; + File file = new File(path); + if (!file.exists()) { + file.mkdirs(); + } + path = path + String.valueOf(System.currentTimeMillis()) + ".jpg"; + os = new FileOutputStream(path); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + os.flush(); + os.close(); + Uri localUri = Uri.fromFile(new File(path)); + Intent localIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, localUri); + context.sendBroadcast(localIntent); + } catch (Exception e) { + e.printStackTrace(); + return false; + } finally { + if (bitmap != null && !bitmap.isRecycled()) { + bitmap.recycle(); + } + StreamUtil.close(os); + } + return true; + } + + private static void clearShare() { + try { + String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + .getAbsolutePath() + File.separator + "开源中国/share/"; + //FileHelper.deleteFileOrDir(path); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onDismiss(DialogInterface dialog) { + if (mBuilder != null && mBuilder.bitmap != null && !mBuilder.bitmap.isRecycled()) { + mBuilder.bitmap.recycle(); + mBuilder.bitmap(null); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/share/SinaShare.java b/app/src/main/java/net/oschina/app/improve/share/SinaShare.java new file mode 100644 index 0000000000000000000000000000000000000000..c3e720e9412b576f4b3af35f79d5e68e167c1909 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/share/SinaShare.java @@ -0,0 +1,91 @@ +package net.oschina.app.improve.share; + +import android.graphics.BitmapFactory; +import android.text.TextUtils; + +import com.sina.weibo.sdk.api.ImageObject; +import com.sina.weibo.sdk.api.TextObject; +import com.sina.weibo.sdk.api.WebpageObject; +import com.sina.weibo.sdk.api.WeiboMultiMessage; +import com.sina.weibo.sdk.api.share.IWeiboShareAPI; +import com.sina.weibo.sdk.api.share.SendMultiMessageToWeiboRequest; +import com.sina.weibo.sdk.api.share.WeiboShareSDK; +import com.sina.weibo.sdk.utils.Utility; + +import net.oschina.app.R; +import net.oschina.app.improve.widget.SimplexToast; + + +/** + * sina + * Created by huanghaibin on 2017/6/12. + */ +@SuppressWarnings("unused") +public class SinaShare extends BaseShare { + + private static final String APP_KEY = "3616966952"; + private static final String APP_SECRET = "fd81f6d31427b467f49226e48a741e28"; + private IWeiboShareAPI mAPI; + + SinaShare(Builder mBuilder) { + super(mBuilder); + mAPI = WeiboShareSDK.createWeiboAPI(mBuilder.mActivity, APP_KEY, false); + mAPI.registerApp(); + mBuilder.isShareImage = false; + } + + @Override + public boolean share() { + if (!mAPI.isWeiboAppInstalled()) { + SimplexToast.show(mBuilder.mActivity, "请安装微博客户端"); + return false; + } + if (mBuilder.isShareImage) { + shareImage(); + return true; + } + toShare(); + return true; + } + + private void toShare() { + WebpageObject webpageObject = new WebpageObject(); + webpageObject.identify = Utility.generateGUID(); + webpageObject.title = mBuilder.title; + webpageObject.description = mBuilder.content; + mBuilder.bitmap = BitmapFactory.decodeResource(mBuilder.mActivity.getResources(), R.mipmap.ic_share_app_logo); + webpageObject.setThumbImage(mBuilder.bitmap); + webpageObject.actionUrl = mBuilder.url; + webpageObject.defaultText = mBuilder.content; + + WeiboMultiMessage weiboMessage = new WeiboMultiMessage(); + + TextObject textObject = new TextObject(); + textObject.text = mBuilder.title; + textObject.title = mBuilder.title; + weiboMessage.textObject = textObject; + + weiboMessage.mediaObject = webpageObject; + + SendMultiMessageToWeiboRequest request = new SendMultiMessageToWeiboRequest(); + request.transaction = String.valueOf(System.currentTimeMillis()); + request.multiMessage = weiboMessage; + mAPI.sendRequest(mBuilder.mActivity, request); + } + + @Override + public void shareImage() { + String url = saveImage(mBuilder.bitmap); + if (TextUtils.isEmpty(url)) return; + WeiboMultiMessage msg = new WeiboMultiMessage(); + ImageObject img = new ImageObject(); + //img.setImageObject(bitmap); + img.imagePath = url; + msg.imageObject = img; + SendMultiMessageToWeiboRequest multRequest = new SendMultiMessageToWeiboRequest(); + multRequest.multiMessage = msg; + //以当前时间戳为唯一识别符 + multRequest.transaction = String.valueOf(System.currentTimeMillis()); + mAPI.sendRequest(mBuilder.mActivity, multRequest); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/share/TencentQQShare.java b/app/src/main/java/net/oschina/app/improve/share/TencentQQShare.java new file mode 100644 index 0000000000000000000000000000000000000000..78191069e7fd22754309795f9e4c107e605107b8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/share/TencentQQShare.java @@ -0,0 +1,83 @@ +package net.oschina.app.improve.share; + +import android.os.Bundle; +import android.text.TextUtils; + +import com.tencent.connect.share.QQShare; +import com.tencent.tauth.IUiListener; +import com.tencent.tauth.Tencent; +import com.tencent.tauth.UiError; + +import net.oschina.app.R; +import net.oschina.app.improve.widget.SimplexToast; + + +/** + * QZoneShare + * Created by huanghaibin on 2017/6/12. + */ +@SuppressWarnings("unused") +public class TencentQQShare extends BaseShare implements IUiListener { + + private static final String APP_ID = "100942993"; + private static final String APP_KEY = "8edd3cc7ca8dcc15082d6fe75969601b"; + + private Tencent tencent; + + TencentQQShare(Builder mBuilder) { + super(mBuilder); + tencent = Tencent.createInstance(APP_ID, mBuilder.mActivity.getApplicationContext()); + } + + @Override + public boolean share() { + if (mBuilder.isShareImage && mBuilder.bitmap != null) { + shareImage(); + } else { + tencent.shareToQQ(mBuilder.mActivity, initShare(), this); + } + return true; + } + + @Override + public void shareImage() { + String url = saveImage(mBuilder.bitmap); + if (TextUtils.isEmpty(url)) return; + Bundle params = new Bundle(); + params.putString(QQShare.SHARE_TO_QQ_IMAGE_LOCAL_URL, url); + params.putString(QQShare.SHARE_TO_QQ_APP_NAME, mBuilder.title); + params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_IMAGE); + tencent.shareToQQ(mBuilder.mActivity, params, this); + } + + private Bundle initShare() { + Bundle params = new Bundle(); + params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_DEFAULT); + params.putString(QQShare.SHARE_TO_QQ_TITLE, mBuilder.title); + params.putString(QQShare.SHARE_TO_QQ_SUMMARY, mBuilder.content); + params.putString(QQShare.SHARE_TO_QQ_TARGET_URL, mBuilder.url); + params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL, mBuilder.imageUrl); + params.putInt(QQShare.SHARE_TO_QQ_IMAGE_LOCAL_URL, R.mipmap.ic_share_app_logo); + if (mBuilder.isShareApp) { + params.putString(QQShare.SHARE_TO_QQ_APP_NAME, mBuilder.title); + params.putInt(QQShare.SHARE_TO_QQ_IMAGE_LOCAL_URL, R.mipmap.ic_share_app_logo); + } + return params; + } + + + @Override + public void onComplete(Object o) { + SimplexToast.show(mBuilder.mActivity, "成功分享"); + } + + @Override + public void onError(UiError uiError) { + SimplexToast.show(mBuilder.mActivity, "分享失败"); + } + + @Override + public void onCancel() { + SimplexToast.show(mBuilder.mActivity, "分享取消"); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/share/WeChatShare.java b/app/src/main/java/net/oschina/app/improve/share/WeChatShare.java new file mode 100644 index 0000000000000000000000000000000000000000..fd849bcae41dc411ed6100e3cf6f15b954591f39 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/share/WeChatShare.java @@ -0,0 +1,110 @@ +package net.oschina.app.improve.share; + +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.support.v4.content.FileProvider; +import android.text.TextUtils; + +import com.tencent.mm.sdk.modelbase.BaseReq; +import com.tencent.mm.sdk.modelbase.BaseResp; +import com.tencent.mm.sdk.modelmsg.SendMessageToWX; +import com.tencent.mm.sdk.modelmsg.WXMediaMessage; +import com.tencent.mm.sdk.modelmsg.WXWebpageObject; +import com.tencent.mm.sdk.openapi.IWXAPI; +import com.tencent.mm.sdk.openapi.IWXAPIEventHandler; +import com.tencent.mm.sdk.openapi.WXAPIFactory; + +import net.oschina.app.BuildConfig; +import net.oschina.app.R; +import net.oschina.app.improve.widget.SimplexToast; + + +import java.io.File; + +/** + * WeChatShare + * Created by huanghaibin on 2017/6/12. + */ + +public class WeChatShare extends BaseShare implements IWXAPIEventHandler { + private static final String APP_ID = "wxa8213dc827399101"; + public static final String APP_SECRET = "5c716417ce72ff69d8cf0c43572c9284"; + private IWXAPI wxAPI; + + WeChatShare(Builder mBuilder) { + super(mBuilder); + wxAPI = WXAPIFactory.createWXAPI(mBuilder.mActivity, APP_ID, false); + wxAPI.handleIntent(mBuilder.mActivity.getIntent(), this); + } + + @Override + public boolean share() { + if (mBuilder.isShareImage && mBuilder.bitmap != null) { + shareImage(); + } else { + wechatShare(0); + } + return false; + } + + @Override + public void onReq(BaseReq baseReq) { + wechatShare(0); + } + + @Override + public void onResp(BaseResp baseResp) { + + switch (baseResp.errCode) { + case BaseResp.ErrCode.ERR_OK: + SimplexToast.show(mBuilder.mActivity, "分享成功"); + break; + case BaseResp.ErrCode.ERR_USER_CANCEL: + SimplexToast.show(mBuilder.mActivity, "取消分享"); + break; + case BaseResp.ErrCode.ERR_AUTH_DENIED: + SimplexToast.show(mBuilder.mActivity, "分享失败"); + break; + } + } + + void wechatShare(int flag) { + + WXWebpageObject webpage = new WXWebpageObject(); + webpage.webpageUrl = mBuilder.url; + final WXMediaMessage msg = new WXMediaMessage(webpage); + if(mBuilder.thumbBitmap!= null && !mBuilder.thumbBitmap.isRecycled()){ + msg.setThumbImage(mBuilder.thumbBitmap); + }else { + msg.setThumbImage(BitmapFactory.decodeResource(mBuilder.mActivity.getResources(), R.mipmap.ic_share_app_logo)); + } + msg.title = mBuilder.title; + msg.description = mBuilder.content; + + final SendMessageToWX.Req req = new SendMessageToWX.Req(); + req.transaction = String.valueOf(System.currentTimeMillis()); + req.message = msg; + req.scene = flag == 0 ? SendMessageToWX.Req.WXSceneSession : SendMessageToWX.Req.WXSceneTimeline; + wxAPI.sendReq(req); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Override + public void shareImage() { + try { + String url = saveImage(mBuilder.bitmap); + if (TextUtils.isEmpty(url)) return; + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + Uri uri = FileProvider.getUriForFile(mBuilder.mActivity, APP_PROVIDER, new File(url)); + intent.putExtra(Intent.EXTRA_STREAM, uri);//uri为你要分享的图片的uri + intent.setType("image/*"); + intent.setClassName("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI"); + mBuilder.mActivity.startActivityForResult(intent, 1); + } catch (Exception e) { + SimplexToast.show(mBuilder.mActivity, "分享失败"); + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/activities/TopicActivity.java b/app/src/main/java/net/oschina/app/improve/tweet/activities/TopicActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..9e6e2b6862c53bfab7bdc1bdfd2307d2ad325f2b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/activities/TopicActivity.java @@ -0,0 +1,38 @@ +package net.oschina.app.improve.tweet.activities; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.tweet.fragments.TweetFragment; + +/** + * 新版话题界面 + * Created by huanghaibin on 2017/10/28. + */ + +public class TopicActivity extends BackActivity { + + public static void show(Context context, int topicType, String topic) { + Intent intent = new Intent(context, TopicActivity.class); + intent.putExtra(TweetFragment.BUNDLE_KEY_REQUEST_CATALOG, topicType); + intent.putExtra(TweetFragment.BUNDLE_KEY_TAG, topic); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_topic; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + TweetFragment fragment = new TweetFragment(); + fragment.setArguments(getIntent().getExtras()); + addFragment(R.id.fl_content, fragment); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/activities/TopicTweetActivity.java b/app/src/main/java/net/oschina/app/improve/tweet/activities/TopicTweetActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..45efe36656fd0534c908688551c51f57b04161fd --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/activities/TopicTweetActivity.java @@ -0,0 +1,245 @@ +package net.oschina.app.improve.tweet.activities; + +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.util.Pair; +import android.support.v4.view.ViewPager; +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.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.bean.simple.TweetComment; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.tweet.fragments.TweetFragment; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.util.UIHelper; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; + +/** + * 动弹话题详情 + * Created by thanatosx on 2016/11/8. + */ + +public class TopicTweetActivity extends BackActivity { + + @Bind(R.id.layout_coordinator) + CoordinatorLayout mLayoutCoordinator; + @Bind(R.id.layout_appbar) + AppBarLayout mLayoutAppBar; + @Bind(R.id.iv_wallpaper) + ImageView mViewWallpaper; + @Bind(R.id.tv_title) + TextView mViewTitle; + @Bind(R.id.tv_mix_title) + TextView mViewMixTitle; + @Bind(R.id.tv_count) + TextView mViewCount; + @Bind(R.id.tv_description) + TextView mViewDescription; + @Bind(R.id.toolbar) + Toolbar mToolbar; + @Bind(R.id.layout_tab) + TabLayout mLayoutTab; + @Bind(R.id.view_pager) + ViewPager mViewPager; + + EditText mViewInput; + private Dialog dialog; + private TabLayoutOffsetChangeListener mOffsetChangerListener; + private CommentBar mDelegation; + + private List> fragments; + private List replies = new ArrayList<>(); + + public static void show(Context context) { + Intent intent = new Intent(context, TopicTweetActivity.class); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_topic_tweet; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mToolbar.setTitle(""); + mToolbar.setSubtitle(""); + mToolbar.setNavigationIcon(R.mipmap.btn_back_normal); + setSupportActionBar(mToolbar); + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + + mViewTitle.setText("#开源中国客户端#"); + mViewMixTitle.setText("#开源中国客户端#"); + mViewCount.setText("共有 212 人参与"); + mViewDescription.setText("你对开源中国客户端有什么看法呢?或者有什么好的idea想与大家分享?不要吝啬你的手指,赶快来忘记我吧!"); + mLayoutAppBar.addOnOffsetChangedListener(mOffsetChangerListener = new TabLayoutOffsetChangeListener()); + + fragments = new ArrayList<>(); + fragments.add(Pair.create("最新", TweetFragment.instantiate(TweetFragment.CATALOG_NEW))); + fragments.add(Pair.create("最热", TweetFragment.instantiate(TweetFragment.CATALOG_HOT))); + + mViewPager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) { + @Override + public Fragment getItem(int position) { + return fragments.get(position).second; + } + + @Override + public int getCount() { + return fragments.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + return fragments.get(position).first; + } + }); + mLayoutTab.setupWithViewPager(mViewPager); + + mDelegation = CommentBar.delegation(this, mLayoutCoordinator); + + mDelegation.getBottomSheet().getEditText().setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL) { + handleKeyDel(); + } + return false; + } + }); + + mDelegation.getBottomSheet().showEmoji(); + mDelegation.getBottomSheet().setCommitListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String content = mDelegation.getBottomSheet().getCommentText().replaceAll("[\\s\\n]+", " "); + if (TextUtils.isEmpty(content)) { + Toast.makeText(TopicTweetActivity.this, "请输入文字", Toast.LENGTH_SHORT).show(); + return; + } + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(TopicTweetActivity.this); + return; + } + if (replies != null && replies.size() > 0) + content = mViewInput.getHint() + ": " + content; + dialog = DialogHelper.getProgressDialog(TopicTweetActivity.this, "正在发表评论..."); + dialog.show(); + } + }); + + + /*mDelegation = KeyboardInputDelegation.delegation(this, mLayoutCoordinator, mBannerView); + mDelegation.setBehavior(new FloatingAutoHideDownBehavior()); + mDelegation.showEmoji(getSupportFragmentManager()); + mDelegation.setAdapter(new KeyboardInputDelegation.KeyboardInputAdapter() { + @Override + public void onSubmit(TextView v, String content) { + // TODO do on submit + } + + @Override + public void onFinalBackSpace(View v) { + // TODO remove @someone + } + });*/ + } + + private void handleKeyDel() { + if (replies == null || replies.size() == 0) return; + replies.remove(replies.size() - 1); + if (replies.size() == 0) { + mViewInput.setHint("发表评论"); + return; + } + mViewInput.setHint("回复: @" + replies.get(0).getAuthor().getName()); + if (replies.size() == 2) { + mViewInput.setHint(mViewInput.getHint() + " @" + replies.get(1).getAuthor() + .getName()); + } + } + + @Override + protected void initData() { + super.initData(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_share, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + // TODO share the topic + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: +// if (!mDelegation.onTurnBack()) return true; + break; + } + return super.onKeyDown(keyCode, event); + } + + private class TabLayoutOffsetChangeListener implements AppBarLayout.OnOffsetChangedListener { + boolean isShow = false; + int mScrollRange = -1; + + @Override + public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { + if (mScrollRange == -1) { + mScrollRange = appBarLayout.getTotalScrollRange(); + } + if (mScrollRange + verticalOffset == 0) { + mViewMixTitle.setVisibility(View.VISIBLE); + isShow = true; + } else if (isShow) { + mViewMixTitle.setVisibility(View.GONE); + isShow = false; + } + } + + public void resetRange() { + mScrollRange = -1; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/activities/TweetDetailActivity.java b/app/src/main/java/net/oschina/app/improve/tweet/activities/TweetDetailActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..45bb23855e9978134678eb597738f336ce327f55 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/activities/TweetDetailActivity.java @@ -0,0 +1,638 @@ +package net.oschina.app.improve.tweet.activities; + +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.AnimationDrawable; +import android.os.Bundle; +import android.support.design.widget.CoordinatorLayout; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.text.style.ForegroundColorSpan; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.TweetComment; +import net.oschina.app.improve.bean.simple.TweetLike; +import net.oschina.app.improve.behavior.CommentBar; +import net.oschina.app.improve.dialog.ShareDialog; +import net.oschina.app.improve.tweet.contract.TweetDetailContract; +import net.oschina.app.improve.tweet.fragments.TweetDetailViewPagerFragment; +import net.oschina.app.improve.tweet.service.TweetPublishService; +import net.oschina.app.improve.tweet.share.TweetShareActivity; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; +import net.oschina.app.improve.user.helper.ContactsCacheManager; +import net.oschina.app.improve.utils.QuickOptionDialogHelper; +import net.oschina.app.improve.utils.parser.StringParser; +import net.oschina.app.improve.utils.parser.TweetParser; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.improve.widget.TweetPicturesLayout; +import net.oschina.app.improve.widget.adapter.OnKeyArrivedListenerAdapterV2; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.PlatfromUtil; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.UIHelper; +import net.oschina.app.widget.RecordButtonUtil; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; +import butterknife.OnLongClick; +import cz.msebera.android.httpclient.Header; + +/** + * 动弹详情 + * Created by thanatos + * on 16/6/13. + */ +@SuppressWarnings("deprecation") +public class TweetDetailActivity extends BackActivity implements TweetDetailContract.Operator { + + public static final String BUNDLE_KEY_TWEET = "BUNDLE_KEY_TWEET"; + + @Bind(R.id.identityView) + IdentityView mIdentityView; + @Bind(R.id.iv_portrait) + PortraitView ivPortrait; + @Bind(R.id.tv_nick) + TextView tvNick; + @Bind(R.id.tv_time) + TextView tvTime; + @Bind(R.id.tv_client) + TextView tvClient; +// @Bind(R.id.iv_thumbup) +// ImageView ivThumbup; + @Bind(R.id.layout_coordinator) + CoordinatorLayout mCoordinatorLayout; + @Bind(R.id.fragment_container) + FrameLayout mFrameLayout; + @Bind(R.id.tweet_img_record) + ImageView mImgRecord; + @Bind(R.id.tweet_tv_record) + TextView mSecondRecord; + @Bind(R.id.tweet_bg_record) + RelativeLayout mRecordLayout; + @Bind(R.id.tv_content) + TextView mContent; + @Bind(R.id.tweet_pics_layout) + TweetPicturesLayout mLayoutGrid; + @Bind(R.id.tv_ref_title) + TextView mViewRefTitle; + @Bind(R.id.tv_ref_content) + TextView mViewRefContent; + @Bind(R.id.layout_ref_images) + TweetPicturesLayout mLayoutRefImages; +// @Bind(R.id.iv_dispatch) +// ImageView mViewDispatch; + @Bind(R.id.layout_ref) + LinearLayout mLayoutRef; + + @Bind(R.id.fl_footer) + FrameLayout mFrameFooter; + private Tweet tweet; + private final List replies = new ArrayList<>(); + private RecordButtonUtil mRecordUtil; + private TextHttpResponseHandler publishAdmireHandler; + private TextHttpResponseHandler publishCommentHandler; + + private TweetDetailContract.ICmnView mCmnViewImp; + private TweetDetailContract.IThumbupView mThumbupViewImp; + private TweetDetailContract.IAgencyView mAgencyViewImp; + + private CommentBar mDelegation; + private boolean mInputDoubleEmpty = false; + + private View.OnClickListener onPortraitClickListener; + private ShareDialog alertDialog; + + public static void show(Context context, Tweet tweet) { + Intent intent = new Intent(context, TweetDetailActivity.class); + intent.putExtra(BUNDLE_KEY_TWEET, tweet); + context.startActivity(intent); + } + + public static void show(Context context, long id) { + Tweet tweet = new Tweet(); + tweet.setId(id); + show(context, tweet); + } + + @Override + protected int getContentView() { + return R.layout.activity_tweet_detail; + } + + @Override + protected boolean initBundle(Bundle bundle) { + tweet = (Tweet) getIntent().getSerializableExtra(BUNDLE_KEY_TWEET); + if (tweet == null) { + AppContext.showToastShort("对象没找到"); + return false; + } + return super.initBundle(bundle); + } + + protected void initData() { + // admire tweet + publishAdmireHandler = new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, + Throwable throwable) { + AppContext.showToastShort(mDelegation.getLikeImage().isSelected() ? "取消失败" : "点赞失败"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + ResultBean result = AppOperator.createGson().fromJson( + responseString, new TypeToken>() { + }.getType()); + if (result != null && result.isSuccess()) { + mDelegation.getLikeImage().setSelected(result.getResult().isLiked()); + mThumbupViewImp.onLikeSuccess(result.getResult().isLiked(), null); + } else { + onFailure(statusCode, headers, responseString, null); + } + } + }; + + // publish tweet comment + publishCommentHandler = new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, + Throwable throwable) { + AppContext.showToastShort("评论失败"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + mCmnViewImp.onCommentSuccess(null); + replies.clear(); // 清除 + + if (mDelegation.getBottomSheet().isSyncToTweet()) { + Tweet tempTweet = tweet; + if (tempTweet == null) return; + TweetPublishService.startActionPublish(TweetDetailActivity.this + , mDelegation.getBottomSheet().getCommentText(), null, + About.buildShare(tempTweet.getId(), OSChinaApi.COMMENT_TWEET)); + } + + AppContext.showToastShort("评论成功"); + mDelegation.setCommentHint("添加评论"); + mDelegation.getBottomSheet().getEditText().setText(""); + mDelegation.getBottomSheet().getEditText().setHint("添加评论"); + mDelegation.getBottomSheet().dismiss(); + } + + @Override + public void onStart() { + super.onStart(); + Tweet tempTweet = tweet; + if (tempTweet != null && tempTweet.getAuthor() != null) + ContactsCacheManager.addRecentCache(tempTweet.getAuthor()); + if (mDelegation == null) return; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(false); + + } + + @Override + public void onFinish() { + super.onFinish(); + if (mDelegation == null) return; + mDelegation.getBottomSheet().dismiss(); + mDelegation.setCommitButtonEnable(true); + } + }; + + OSChinaApi.getTweetDetail(tweet.getId(), new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, + Throwable throwable) { + Toast.makeText(TweetDetailActivity.this, "获取数据失败", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + ResultBean result = AppOperator.createGson().fromJson( + responseString, new TypeToken>() { + }.getType()); + if (result.isSuccess()) { + if (result.getResult() == null) { + AppContext.showToast(R.string.tweet_detail_data_null); + finish(); + return; + } + tweet = result.getResult(); + mAgencyViewImp.resetCmnCount(tweet.getCommentCount()); + mAgencyViewImp.resetLikeCount(tweet.getLikeCount()); + setupDetailView(); + } else { + onFailure(500, headers, "妈的智障", null); + } + } + }); + + } + + @Override + protected void initWidget() { + super.initWidget(); + setSwipeBackEnable(true); + setStatusBarDarkMode(); + setDarkToolBar(); + mToolBar.setTitle("动弹详情"); + setSupportActionBar(mToolBar); + + mDelegation = CommentBar.delegation(this, mFrameFooter); + + mDelegation.hideFav(); + mDelegation.hideCommentCount(); + mDelegation.showLike(); + mDelegation.showDispatch(); + mDelegation.setLikeListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onClickThumbUp(); + } + }); + mDelegation.setDispatchListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onClickTransmit(); + } + }); + mDelegation.getBottomSheet().getEditText().setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) { + handleKeyDel(); + } + return false; + } + }); + + mDelegation.hideCommentCount(); + mDelegation.hideFav(); + + mDelegation.getBottomSheet().setMentionListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (AccountHelper.isLogin()) { + UserSelectFriendsActivity.show(TweetDetailActivity.this, mDelegation.getBottomSheet().getEditText()); + } else + LoginActivity.show(TweetDetailActivity.this); + } + }); + + mDelegation.getBottomSheet().getEditText().setOnKeyArrivedListener(new OnKeyArrivedListenerAdapterV2(this)); + mDelegation.getBottomSheet().showEmoji(); + mDelegation.getBottomSheet().hideSyncAction(); + mDelegation.getBottomSheet().setCommitListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String content = mDelegation.getBottomSheet().getCommentText().replaceAll("[\\s\\n]+", " "); + if (TextUtils.isEmpty(content)) { + Toast.makeText(TweetDetailActivity.this, "请输入文字", Toast.LENGTH_SHORT).show(); + return; + } + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(TweetDetailActivity.this); + return; + } + if (replies.size() > 0) + content = mDelegation.getBottomSheet().getEditText().getHint() + ": " + content; + OSChinaApi.pubTweetComment(tweet.getId(), content, 0, publishCommentHandler); + } + }); + resolveVoice(); + setupDetailView(); + + TweetDetailViewPagerFragment mPagerFrag = TweetDetailViewPagerFragment.instantiate(); + mCmnViewImp = mPagerFrag.getCommentViewHandler(); + mThumbupViewImp = mPagerFrag.getThumbupViewHandler(); + mAgencyViewImp = mPagerFrag.getAgencyViewHandler(); + getSupportFragmentManager().beginTransaction() + .replace(R.id.fragment_container, mPagerFrag) + .commit(); + } + + private void resolveVoice() { + if (tweet == null || tweet.getAudio() == null || tweet.getAudio().length == 0) return; + mRecordLayout.setVisibility(View.VISIBLE); + final AnimationDrawable drawable = (AnimationDrawable) mImgRecord.getBackground(); + mRecordLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (tweet == null) return; + getRecordUtil().startPlay(tweet.getAudio()[0].getHref(), mSecondRecord); + } + }); + getRecordUtil().setOnPlayListener(new RecordButtonUtil.OnPlayListener() { + @Override + public void stopPlay() { + drawable.stop(); + mImgRecord.setBackgroundDrawable(drawable.getFrame(0)); + } + + @Override + public void starPlay() { + drawable.start(); + mImgRecord.setBackgroundDrawable(drawable); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_detail, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + if (tweet == null || tweet.getId() <= 0 || TextUtils.isEmpty(tweet.getContent())) + break; + + String content = StringParser.getInstance().parse(this, tweet.getContent()).toString(); + if (content.length() > 30) + content = content.substring(0, 30); + + if (alertDialog == null){ + alertDialog = new ShareDialog(this, true) + .title(content + " - 开源中国社区 ") + .content(content) + .url(tweet.getHref()).with(); + alertDialog.setItemClickListener(new ShareDialog.ShareItemClickListener() { + @Override + public void onShareTweet() { + TweetShareActivity.show(TweetDetailActivity.this,tweet, + mCmnViewImp.getComments()); + } + }); + } + + alertDialog.setTweet(tweet); + alertDialog.show(); + + break; + } + return super.onOptionsItemSelected(item); + } + + private RecordButtonUtil getRecordUtil() { + if (mRecordUtil == null) { + mRecordUtil = new RecordButtonUtil(); + } + return mRecordUtil; + } + + /** + * 填充数据 + */ + private void setupDetailView() { + // 有可能传入的tweet只有id这一个值 + if (tweet == null || isDestroy()) + return; + Author author = tweet.getAuthor(); + mIdentityView.setup(author); + if (author != null) { + ivPortrait.setup(author); + ivPortrait.setOnClickListener(getOnPortraitClickListener()); + tvNick.setText(author.getName()); + } else { + ivPortrait.setup(0, "匿名用户", ""); + tvNick.setText("匿名用户"); + } + if (!TextUtils.isEmpty(tweet.getPubDate())) + tvTime.setText(StringUtils.formatSomeAgo(tweet.getPubDate())); + PlatfromUtil.setPlatFromString(tvClient, tweet.getAppClient()); + if (tweet.isLiked()) { + mDelegation.getLikeImage().setSelected(true); + } else { + mDelegation.getLikeImage().setSelected(false); + } + if (!TextUtils.isEmpty(tweet.getContent())) { + String content = tweet.getContent().replaceAll("[\n\\s]+", " "); + mContent.setText(TweetParser.getInstance().parse(this, content)); + mContent.setMovementMethod(LinkMovementMethod.getInstance()); + } + + mLayoutGrid.setImage(tweet.getImages()); + + /* -- about reference -- */ + if (tweet.getAbout() != null) { + mLayoutRef.setVisibility(View.VISIBLE); + About about = tweet.getAbout(); + mLayoutRefImages.setImage(about.getImages()); + + if (!About.check(about)) { + mViewRefTitle.setVisibility(View.VISIBLE); + mViewRefTitle.setText("不存在或已删除的内容"); + mViewRefContent.setText("抱歉,该内容不存在或已被删除"); + } else { + if (about.getType() == OSChinaApi.COMMENT_TWEET) { + mViewRefTitle.setVisibility(View.GONE); + String aName = "@" + about.getTitle(); + String cnt = about.getContent(); + Spannable spannable = TweetParser.getInstance().parse(this, cnt); + SpannableStringBuilder builder = new SpannableStringBuilder(); + builder.append(aName).append(": "); + builder.append(spannable); + ForegroundColorSpan span = new ForegroundColorSpan( + getResources().getColor(R.color.day_colorPrimary)); + builder.setSpan(span, 0, aName.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + mViewRefContent.setText(builder); + } else { + mViewRefTitle.setVisibility(View.VISIBLE); + mViewRefTitle.setText(about.getTitle()); + mViewRefContent.setText(about.getContent()); + } + } + } else { + mLayoutRef.setVisibility(View.GONE); + } + } + + private View.OnClickListener getOnPortraitClickListener() { + if (onPortraitClickListener == null) { + onPortraitClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + UIHelper.showUserCenter(TweetDetailActivity.this, tweet.getAuthor().getId(), + tweet.getAuthor().getName()); + } + }; + } + return onPortraitClickListener; + } + + @Override + public Tweet getTweetDetail() { + return tweet; + } + + @Override + public void toReply(TweetComment comment) { + if (comment.getAuthor() == null) + return; + if (checkLogin()) return; + if (replies.size() < 5) { + for (TweetComment cmm : replies) { + if (cmm.getAuthor().getId() == comment.getAuthor().getId()) { + this.mDelegation.performClick(); + return; + } + } + if (replies.size() == 0) { + mDelegation.getBottomSheet().getEditText().setHint("回复: @" + comment.getAuthor().getName()); + mDelegation.setCommentHint(mDelegation.getBottomSheet().getEditText().getHint().toString()); + } else { + mDelegation.getBottomSheet().getEditText().setHint(mDelegation.getBottomSheet().getEditText().getHint() + " @" + comment.getAuthor().getName()); + mDelegation.setCommentHint(mDelegation.getBottomSheet().getEditText().getHint().toString()); + } + this.replies.add(comment); + } + this.mDelegation.performClick(); + } + + @Override + public void onScroll() { + if (mDelegation != null) mDelegation.getBottomSheet().dismiss(); + } + + + private void onClickThumbUp() { + if (checkLogin()) return; + OSChinaApi.reverseTweetLike(tweet.getId(), publishAdmireHandler); + } + + @OnClick(R.id.layout_ref) + void onClickRef() { + if (tweet.getAbout() == null) return; + UIHelper.showDetail(this, tweet.getAbout().getType(), tweet.getAbout().getId(), null); + } + + + private void onClickComment() { + if (checkLogin()) return; + replies.clear(); + mDelegation.getBottomSheet().show("发表评论"); + } + + + private void onClickTransmit() { + if (tweet == null || tweet.getId() <= 0 && tweet.getAuthor() == null) return; + + String content = null; + About.Share share; + if (tweet.getAbout() == null) { + share = About.buildShare(tweet.getId(), OSChinaApi.CATALOG_TWEET); + share.title = tweet.getAuthor().getName(); + share.content = tweet.getContent(); + } else { + share = About.buildShare(tweet.getAbout()); + content = "//@" + tweet.getAuthor().getName() + " :" + tweet.getContent(); + content = TweetParser.getInstance().clearHtmlTag(content).toString(); + } + share.commitTweetId = tweet.getId(); + share.fromTweetId = tweet.getId(); + TweetPublishActivity.show(this, null, content, share); + } + + @OnLongClick({R.id.layout_container, R.id.tv_content}) + boolean onLongClickContent() { + QuickOptionDialogHelper.with(this) + .addCopy(HTMLUtil.delHTMLTag(tweet.getContent())) + .show(); + return true; + } + + private boolean checkLogin() { + if (!AccountHelper.isLogin()) { + LoginActivity.show(this); + return true; + } + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + // if (!mDelegation.onTurnBack()) return true; + break; + } + return super.onKeyDown(keyCode, event); + } + + private void handleKeyDel() { + if (replies.size() == 0) return; + if (TextUtils.isEmpty(mDelegation.getBottomSheet().getCommentText())) { + if (mInputDoubleEmpty) { + replies.remove(replies.size() - 1); + if (replies.size() == 0) { + mDelegation.getBottomSheet().getEditText().setHint("发表评论"); + mDelegation.setCommentHint(mDelegation.getBottomSheet().getEditText().getHint().toString()); + return; + } + TweetComment comment = replies.get(0); + if (comment == null || comment.getAuthor() == null) { + mDelegation.getBottomSheet().getEditText().setHint("发表评论"); + mDelegation.setCommentHint(mDelegation.getBottomSheet().getEditText().getHint().toString()); + return; + } + + mDelegation.getBottomSheet().getEditText().setHint("回复: @" + comment.getAuthor().getName()); + for (int i = 1; i < replies.size(); i++) { + TweetComment tc = replies.get(i); + if (tc != null && tc.getAuthor() != null) + mDelegation.getBottomSheet().getEditText().setHint(mDelegation.getBottomSheet().getEditText().getHint() + " @" + tc.getAuthor() + .getName()); + } + } else { + mInputDoubleEmpty = true; + } + } else { + mInputDoubleEmpty = false; + } + + } + + @Override + protected void onStop() { + super.onStop(); + if (alertDialog != null) { + alertDialog.cancelLoading(); + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/activities/TweetPublishActivity.java b/app/src/main/java/net/oschina/app/improve/tweet/activities/TweetPublishActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..f7d6d1decca88e76a2c08e17ac483ec6c73c2779 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/activities/TweetPublishActivity.java @@ -0,0 +1,298 @@ +package net.oschina.app.improve.tweet.activities; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore; +import android.support.annotation.Size; +import android.support.v4.app.FragmentTransaction; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; + +import net.oschina.app.BuildConfig; +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.base.activities.BaseBackActivity; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.tweet.contract.TweetPublishContract; +import net.oschina.app.improve.tweet.fragments.TweetPublishFragment; +import net.oschina.app.improve.tweet.service.TweetPublishService; +import net.oschina.app.util.UIHelper; +import net.oschina.common.utils.CollectionUtil; + +import java.io.File; +import java.util.ArrayList; + +/** + * Created by JuQiu + * on 16/8/22. + */ +public class TweetPublishActivity extends BaseBackActivity { + private static final String TAG = "TweetPublishActivity"; + private TweetPublishContract.View mView; + + public static void show(Context context) { + show(context, null); + } + + public static void show(Context context, View view) { + show(context, view, null); + } + + public static void show(Context context, View view, String defaultContent) { + show(context, view, defaultContent, null); + } + + public static void show(Context context, View view, String defaultContent, About.Share share) { + int[] location = new int[]{0, 0}; + int[] size = new int[]{0, 0}; + + if (view != null) { + view.getLocationOnScreen(location); + size[0] = view.getWidth(); + size[1] = view.getHeight(); + } + + show(context, location, size, defaultContent, share, null); + } + + @SuppressWarnings("unused") + public static void show(Context context, boolean isLocalShare, String localImageUrl) { + show(context, null, null, null, null, localImageUrl); + } + + + public static void show(Context context, @Size(2) int[] viewLocationOnScreen, + @Size(2) int[] viewSize, String defaultContent, About.Share share, String localImageUrl) { + // Check login before show + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(context); + return; + } + + Intent intent = new Intent(context, TweetPublishActivity.class); + + if (viewLocationOnScreen != null) { + intent.putExtra("location", viewLocationOnScreen); + } + if (viewSize != null) { + intent.putExtra("size", viewSize); + } + if (defaultContent != null) { + intent.putExtra("defaultContent", defaultContent); + } + if (share != null) { + intent.putExtra("aboutShare", share); + } + if (!TextUtils.isEmpty(localImageUrl)) { + intent.putExtra("imageUrl", localImageUrl); + } + context.startActivity(intent); + } + + @Override + protected int getContentView() { + // hide the software + // getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + return R.layout.activity_tweet_publish; + } + + @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection", "unchecked", "ResultOfMethodCallIgnored"}) + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); + + Intent intent = getIntent(); + Bundle bundle = intent.getExtras(); + + if (bundle == null) bundle = new Bundle(); + // Read other data + readFastShareByOther(bundle, intent); + + TweetPublishFragment fragment = new TweetPublishFragment(); + // init the args bounds + fragment.setArguments(bundle); + FragmentTransaction trans = getSupportFragmentManager() + .beginTransaction(); + trans.replace(R.id.activity_tweet_publish, fragment); + trans.commit(); + mView = fragment; + } + + /** + * 读取快速分享到当前界面的内容 + * + * @param intent 需要写入源 + */ + private void readFastShareByOther(Bundle bundle, Intent intent) { + // Check + if (intent == null) + return; + String type = intent.getType(); + if (TextUtils.isEmpty(type)) + return; + + //判断当前分享的内容是文本,还是图片 + if ("text/plain".equals(type)) { + String text = intent.getStringExtra(Intent.EXTRA_TEXT); + bundle.putString("defaultContent", text); + } else if (type.startsWith("image/")) { + ArrayList uris = new ArrayList<>(); + Object obj = intent.getExtras().get(Intent.EXTRA_STREAM); + if (obj instanceof Uri) { + Uri uri = (Uri) obj; + String decodePath = decodePath(uri); + if (decodePath != null) + uris.add(decodePath); + } else { + try { + @SuppressWarnings("unchecked") + ArrayList list = (ArrayList) obj; + //大于9张图片的分享,直接只使用前9张 + if (list != null && list.size() > 0) { + for (int i = 0, len = list.size(); i < len; i++) { + if (i > 9) { + break; + } + String decodePath = decodePath(list.get(i)); + if (decodePath != null) + uris.add(decodePath); + } + } + } catch (Exception e) { + if (BuildConfig.DEBUG) + e.printStackTrace(); + } + } + if (uris.size() > 0) { + String[] paths = CollectionUtil.toArray(uris, String.class); + bundle.putStringArray("defaultImages", paths); + } + } + } + + /** + * 通过uri当中的唯一id搜索本地相册图片,是否真的存在。然后返回真实的path路径 + * + * @param uri rui + * @return path + */ + private String decodePath(Uri uri) { + String decodePath = null; + String uriPath = uri.toString(); + + if (uriPath != null && uriPath.startsWith("content://")) { + int id = 0; + try { + id = Integer.parseInt(uriPath.substring(uriPath.lastIndexOf("/") + 1, uriPath.length())); + } catch (Exception e) { + e.printStackTrace(); + return parseUri(uri); + } + + Uri tempUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + String[] projection = {MediaStore.Images.Media.DATA}; + String selection = MediaStore.Images.Media._ID + "=?"; + String[] selectionArgs = {id + ""}; + + Cursor cursor = getContentResolver().query(tempUri, projection, selection, selectionArgs, null); + try { + while (cursor != null && cursor.moveToNext()) { + String temp = cursor.getString(0); + File file = new File(temp); + if (file.exists()) { + decodePath = temp; + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + + } else { + return uriPath; + } + return decodePath; + } + + private String parseUri(Uri uri) { + String path = uri.getPath(); + Log.e("path", "path" + path); + if (path != null) { + File file = new File(path.replace("/raw/", "")); + return file.exists() ? file.getPath() : ""; + } + return ""; + } + + @Override + protected void initData() { + super.initData(); + // before the fragment show + registerPublishStateReceiver(); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + //暂不处理已在当前界面下的分享 + } + + @Override + protected void onPause() { + unRegisterPublishStateReceiver(); + super.onPause(); + } + + private void registerPublishStateReceiver() { + if (mPublishStateReceiver != null) + return; + IntentFilter intentFilter = new IntentFilter(TweetPublishService.ACTION_RECEIVER_SEARCH_FAILED); + BroadcastReceiver receiver = new SearchReceiver(); + registerReceiver(receiver, intentFilter); + mPublishStateReceiver = receiver; + + // start search + TweetPublishService.startActionSearchFailed(this); + } + + private void unRegisterPublishStateReceiver() { + final BroadcastReceiver receiver = mPublishStateReceiver; + mPublishStateReceiver = null; + if (receiver != null) + unregisterReceiver(receiver); + } + + private BroadcastReceiver mPublishStateReceiver; + + private class SearchReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (TweetPublishService.ACTION_RECEIVER_SEARCH_FAILED.equals(intent.getAction())) { + String[] ids = intent.getStringArrayExtra(TweetPublishService.EXTRA_IDS); + if (ids == null || ids.length == 0) + return; + TweetPublishQueueActivity.show(TweetPublishActivity.this, ids); + } + } + } + + @Override + public void onBackPressed() { + //super.onBackPressed(); + if (mView != null && mView.onBackPressed()) { + mView.getOperator().onBack(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/activities/TweetPublishQueueActivity.java b/app/src/main/java/net/oschina/app/improve/tweet/activities/TweetPublishQueueActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..245c5c7dc36dccfc94d1c345d842cfe12318356a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/activities/TweetPublishQueueActivity.java @@ -0,0 +1,116 @@ +package net.oschina.app.improve.tweet.activities; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BaseBackActivity; +import net.oschina.app.improve.tweet.adapter.TweetQueueAdapter; +import net.oschina.app.improve.tweet.service.TweetPublishCache; +import net.oschina.app.improve.tweet.service.TweetPublishModel; +import net.oschina.app.improve.tweet.service.TweetPublishService; +import net.oschina.common.widget.Loading; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; + +public class TweetPublishQueueActivity extends BaseBackActivity implements TweetQueueAdapter.Callback, View.OnClickListener { + @Bind(R.id.loading) + Loading mLoading; + @Bind(R.id.txt_title) + TextView mTitle; + @Bind(R.id.recycler) + RecyclerView mRecycler; + private TweetQueueAdapter mAdapter; + + public static void show(Context context, String[] ids) { + Intent intent = new Intent(context, TweetPublishQueueActivity.class); + intent.putExtra(TweetPublishService.EXTRA_IDS, ids); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_tweet_publish_queue; + } + + @Override + protected void initWidget() { + super.initWidget(); + mRecycler.setLayoutManager(new LinearLayoutManager(this)); + mAdapter = new TweetQueueAdapter(this); + mRecycler.setAdapter(mAdapter); + } + + @Override + protected boolean initBundle(Bundle bundle) { + if (bundle != null) { + final String[] ids = bundle.getStringArray(TweetPublishService.EXTRA_IDS); + if (ids != null && ids.length > 0) { + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + Context context = getApplicationContext(); + List models = new ArrayList<>(); + for (String str : ids) { + TweetPublishModel model = TweetPublishCache.get(context, str); + if (model != null) + models.add(model); + } + if (models.size() > 0) + addData(models); + else + finish(); + } + }); + return true; + } + } + return false; + } + + private void addData(final List models) { + runOnUiThread(new Runnable() { + @Override + public void run() { + mAdapter.add(models); + mLoading.setVisibility(View.GONE); + mTitle.setVisibility(View.VISIBLE); + mRecycler.setVisibility(View.VISIBLE); + + mTitle.setText(String.format("『%s』Todo", models.size())); + } + }); + } + + @Override + public void onClickContinue(TweetPublishModel model) { + TweetPublishService.startActionContinue(this, model.getId()); + if (mAdapter.getItemCount() == 0) + finish(); + } + + @Override + public void onClickDelete(TweetPublishModel model) { + TweetPublishService.startActionDelete(this, model.getId()); + if (mAdapter.getItemCount() == 0) + finish(); + } + + @OnClick(R.id.icon_back) + @Override + public void onClick(View v) { + if (v.getId() == R.id.icon_back) { + onSupportNavigateUp(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/activities/TweetTopicActivity.java b/app/src/main/java/net/oschina/app/improve/tweet/activities/TweetTopicActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..89728c46bee07af16df79b459e6ff178ae9d36b7 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/activities/TweetTopicActivity.java @@ -0,0 +1,469 @@ +package net.oschina.app.improve.tweet.activities; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.support.v4.app.Fragment; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.app.ParentLinkedHolder; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.activities.BaseBackActivity; +import net.oschina.app.improve.tweet.fragments.TweetPublishFragment; +import net.oschina.app.improve.utils.CacheManager; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.utils.parser.RichTextParser; +import net.oschina.app.improve.widget.RichEditText; +import net.oschina.common.adapter.TextWatcherAdapter; +import net.oschina.common.utils.CollectionUtil; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import butterknife.Bind; + +public class TweetTopicActivity extends BackActivity { + private static final String CACHE_FILE = "TweetTopicLocalCache"; + @Bind(R.id.edit_enter_tag) + EditText mTopicContent; + + @Bind(R.id.recycler) + RecyclerView mRecycler; + + private List mHotList = new ArrayList<>(); + private List mLocalList = new ArrayList<>(); + private LinkedList mCache; + private String[] mLabels = new String[]{"热门", "本地"}; + + private static ParentLinkedHolder textParentLinkedHolder; + + public static void show(Object starter, RichEditText editText) { + if (editText != null && (starter instanceof Activity || starter instanceof Fragment || starter instanceof android.app.Fragment)) { + synchronized (TweetTopicActivity.class) { + ParentLinkedHolder holder = new ParentLinkedHolder<>(editText); + textParentLinkedHolder = holder.addParent(textParentLinkedHolder); + } + + if (starter instanceof Activity) { + Activity context = (Activity) starter; + Intent intent = new Intent(context, TweetTopicActivity.class); + context.startActivityForResult(intent, TweetPublishFragment.REQUEST_CODE_SELECT_TOPIC); + } else if (starter instanceof Fragment) { + Fragment fragment = (Fragment) starter; + Context context = fragment.getContext(); + if (context == null) + return; + Intent intent = new Intent(context, TweetTopicActivity.class); + fragment.startActivityForResult(intent, TweetPublishFragment.REQUEST_CODE_SELECT_TOPIC); + } else { + android.app.Fragment fragment = (android.app.Fragment) starter; + Context context = fragment.getActivity(); + if (context == null) + return; + Intent intent = new Intent(context, TweetTopicActivity.class); + fragment.startActivityForResult(intent, TweetPublishFragment.REQUEST_CODE_SELECT_TOPIC); + } + } + } + + @Override + protected int getContentView() { + return R.layout.activity_tweet_topic; + } + + private LinearLayoutManager mLayoutManager; + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mRecycler.setLayoutManager(mLayoutManager = new LinearLayoutManager(this)); + mRecycler.setAdapter(adapter); + mTopicContent.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.ACTION_DOWN) { + onSubmit(); + return true; + } + return false; + } + }); + mTopicContent.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == KeyEvent.ACTION_DOWN || actionId == EditorInfo.IME_ACTION_DONE) { + onSubmit(); + return true; + } + return false; + } + }); + mTopicContent.addTextChangedListener(new TextWatcherAdapter() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + sortLocalList(getContent()); + } + }); + } + + @Override + protected void initData() { + super.initData(); + + String[] topics = loadHotCache(getResources()); + for (String topic : topics) { + if (TextUtils.isEmpty(topic)) + continue; + mHotList.add(new TopicBean(topic)); + } + + loadCache(); + + adapter.notifyDataSetChanged(); + } + + private static String[] loadHotCache(Resources resources) { + return resources.getStringArray(R.array.topic_list); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_tweet_topic, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_submit) { + onSubmit(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private String getContent() { + return mTopicContent.getText() + .toString().trim().replace("#", ""); + } + + private void loadCache() { + mCache = loadCache(this); + int size = mCache.size(); + for (int i = 0; i < size; i++) { + mLocalList.add(new TopicBean(mCache.get(i), true)); + } + } + + private static LinkedList loadCache(Context context) { + List cache = CacheManager.readListJson(context, CACHE_FILE, String.class); + LinkedList linkedList = new LinkedList<>(); + if (cache != null) + linkedList.addAll(cache); + return linkedList; + } + + public static void saveCache(Context context, String... strs) { + final ArrayList hotCaches = CollectionUtil.toArrayList(loadHotCache(context.getResources())); + final LinkedList localCache = loadCache(context); + + // 避免重复添加 + for (String str : strs) { + if (TextUtils.isEmpty(str) || TextUtils.isEmpty(str = str.trim())) + continue; + if (!hotCaches.contains(str) && !localCache.contains(str)) { + localCache.addFirst(str); + } + } + + // 至多存储15条 + while (localCache.size() > 15) { + localCache.removeLast(); + } + + CacheManager.saveToJson(context, CACHE_FILE, localCache); + } + + private void onSubmit() { + String str = getContent(); + if (TextUtils.isEmpty(str)) + finish(); + else { + saveCache(this, str); + doResult(str); + } + } + + private void doResult(String topic) { + synchronized (TweetTopicActivity.class) { + if (textParentLinkedHolder != null) { + RichEditText editText = textParentLinkedHolder.item; + if (editText != null) + editText.appendTopic(topic); + } + } + + Intent result = new Intent(); + result.putExtra("data", topic); + setResult(RESULT_OK, result); + + finish(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + synchronized (TweetTopicActivity.class) { + if (textParentLinkedHolder != null) { + textParentLinkedHolder = textParentLinkedHolder.putParent(); + } + } + } + + private void doDeleteCache(TopicBean bean, boolean clear) { + final LinkedList cache = mCache; + final List cacheLocal = mLocalList; + if (clear) { + cache.clear(); + cacheLocal.clear(); + } else { + Iterator itr = cacheLocal.iterator(); + while (itr.hasNext()) { + if (itr.next().equals(bean)) + itr.remove(); + } + Iterator itrCache = cache.iterator(); + while (itrCache.hasNext()) { + if (itrCache.next().equals(bean.text)) + itrCache.remove(); + } + } + CacheManager.saveToJson(this, CACHE_FILE, cache); + adapter.notifyDataSetChanged(); + } + + private void sortLocalList(String text) { + if (mLocalList.size() == 0) + return; + + boolean isEmpty = TextUtils.isEmpty(text); + final String py = isEmpty ? "!#" : RichTextParser.convertToPinyin(text, SPLIT_HEAD); + Pattern pattern = Pattern.compile(py); + + for (TopicBean bean : mLocalList) { + Matcher matcher = pattern.matcher(bean.py); + if (matcher.find()) { + bean.sort = matcher.start(); + } else { + bean.sort = ORDER_MAX; + } + } + + Collections.sort(mLocalList, new Comparator() { + @Override + public int compare(TopicBean o1, TopicBean o2) { + if (o1.sort == ORDER_MAX && o2.sort == ORDER_MAX) { + return o1.order - o2.order; + } + return o1.sort - o2.sort; + } + }); + + adapter.notifyDataSetChanged(); + + if (isEmpty) + mLayoutManager.scrollToPosition(0); + else { + mLayoutManager.scrollToPositionWithOffset(mHotList.size() + 1, 0); + } + } + + private RecyclerView.Adapter adapter = new RecyclerView.Adapter() { + @Override + public int getItemViewType(int position) { + if (get(position) instanceof String) { + return R.layout.list_item_sample_label; + } else { + if (position == getItemCount() - 1 || position == mHotList.size()) + return 0; + else + return R.layout.list_item_topic; + } + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + switch (viewType) { + case R.layout.list_item_sample_label: + return new LabelHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.list_item_sample_label, parent, false)); + case R.layout.list_item_topic: { + View root = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.list_item_topic, parent, false); + return new DataHolder(root, true); + } + default: + View root = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.list_item_topic, parent, false); + return new DataHolder(root, false); + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder instanceof LabelHolder) { + ((LabelHolder) holder).set((String) get(position)); + } else { + TopicBean bean = (TopicBean) get(position); + ((DataHolder) holder).set(bean); + // Set tag + holder.itemView.setTag(bean); + } + } + + @Override + public int getItemCount() { + int hotCount = mHotList.size(); + int localCount = mLocalList.size(); + return hotCount + localCount + (localCount > 0 ? 2 : 1); + } + + private Object get(int position) { + if (position == 0) + return mLabels[0]; + + int hotCount = mHotList.size(); + int localCount = mLocalList.size(); + + if (localCount > 0) { + if (position == hotCount + 1) + return mLabels[1]; + if ((position >= hotCount + 2)) + return mLocalList.get(position - hotCount - 2); + } + + return mHotList.get(position - 1); + } + }; + + private class LabelHolder extends RecyclerView.ViewHolder { + private TextView mText; + + LabelHolder(View itemView) { + super(itemView); + mText = (TextView) itemView.findViewById(R.id.txt_string); + } + + + void set(String data) { + mText.setText(data); + } + } + + private class DataHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { + private TextView mTitle; + + + DataHolder(View itemView, boolean needLine) { + super(itemView); + mTitle = (TextView) itemView.findViewById(R.id.txt_title); + if (needLine) + itemView.findViewById(R.id.line).setVisibility(View.VISIBLE); + itemView.setOnClickListener(this); + itemView.setOnLongClickListener(this); + } + + void set(TopicBean data) { + mTitle.setText(data.text); + } + + @Override + public void onClick(View v) { + Object obj = v.getTag(); + if (obj != null && obj instanceof TopicBean) { + doResult(((TopicBean) obj).text); + } + } + + @Override + public boolean onLongClick(View v) { + Object obj = v.getTag(); + if (obj != null && obj instanceof TopicBean) { + final TopicBean bean = (TopicBean) obj; + if (!bean.isLocal) + return false; + + String[] items = new String[2]; + items[0] = getResources().getString(R.string.delete); + items[1] = getResources().getString(R.string.delete_all); + DialogHelper.getSelectDialog(TweetTopicActivity.this, items, "取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + if (i == 0) { + doDeleteCache(bean, false); + } else if (i == 1) { + doDeleteCache(null, true); + } + } + }).show(); + + return true; + } + return false; + } + } + + private static int ORDER_COUNT = 0; + private static int ORDER_MAX = 100; + private static final String SPLIT_HEAD = "~"; + + private class TopicBean { + String text; + String py; + private boolean isLocal; + private int sort = ORDER_MAX; + private int order = ORDER_COUNT++; + + TopicBean(String text) { + this.text = text; + this.py = RichTextParser.convertToPinyin(text, SPLIT_HEAD); + } + + TopicBean(String text, boolean isLocal) { + this(text); + this.isLocal = isLocal; + } + + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof TopicBean)) + return false; + String oth = ((TopicBean) obj).text; + if (oth == null) + return this.text == null; + return oth.equals(this.text); + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/adapter/SoftwareTweetAdapter.java b/app/src/main/java/net/oschina/app/improve/tweet/adapter/SoftwareTweetAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..a2c0eec0efacc9e54384cb3cae8c246f3cf429ac --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/adapter/SoftwareTweetAdapter.java @@ -0,0 +1,189 @@ +package net.oschina.app.improve.tweet.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.TweetLikeReverse; +import net.oschina.app.improve.comment.CommentsUtil; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.PlatfromUtil; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; +import net.oschina.app.widget.TweetTextView; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import butterknife.ButterKnife; +import cz.msebera.android.httpclient.Header; + +/** + * Created by fei + * on 2016/7/20. + */ +public class SoftwareTweetAdapter extends BaseRecyclerAdapter implements View.OnClickListener { + + private RequestManager requestManager; + + public SoftwareTweetAdapter(Context context, int mode) { + super(context, mode); + setState(BaseRecyclerAdapter.STATE_LOADING, false); + requestManager = Glide.with(context); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new SoftwareTweetViewHolder(mInflater.inflate(R.layout.item_list_tweet_improve, parent, false)); + } + + @SuppressWarnings("deprecation") + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Tweet item, int position) { + SoftwareTweetViewHolder vh = (SoftwareTweetViewHolder) holder; + + vh.icon.setTag(R.id.iv_tweet_face, position); + final Author author = item.getAuthor(); + vh.mIdentityView.setup(author); + if (author == null) { + vh.icon.setup(0, "匿名用户", ""); + vh.name.setText("匿名用户"); + } else { + vh.icon.setup(author); + vh.icon.setOnClickListener(this); + vh.name.setText(author.getName()); + } + + CommentsUtil.formatHtml(mContext.getResources(), vh.content, item.getContent()); + vh.pubTime.setText(StringUtils.formatSomeAgo(item.getPubDate())); + PlatfromUtil.setPlatFromString(vh.deviceType, item.getAppClient()); + boolean liked = item.isLiked(); + if (liked) { + vh.likeStatus.setImageResource(R.mipmap.ic_thumbup_actived); + } else { + vh.likeStatus.setImageResource(R.mipmap.ic_thumb_normal); + } + vh.likeStatus.setTag(position); + vh.likeStatus.setOnClickListener(this); + vh.likeCount.setText(String.format("%s", item.getLikeCount())); + vh.commentCount.setText(String.format("%s", item.getCommentCount())); + + } + + @Override + public void setOnItemClickListener(OnItemClickListener onItemClickListener) { + super.setOnItemClickListener(onItemClickListener); + + } + + @Override + public void onClick(View v) { + int id = v.getId(); + switch (id) { + case R.id.iv_tweet_face: + int p = (int) v.getTag(R.id.iv_tweet_face); + final Tweet item = getItem(p); + OtherUserHomeActivity.show(mContext, item.getAuthor()); + break; + case R.id.iv_like_state: + int position = (int) v.getTag(); + final Tweet tempItem = getItem(position); + requestEventDispatcher(tempItem); + break; + default: + break; + } + } + + /** + * + */ + private void requestEventDispatcher(final Tweet item) { + + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(mContext); + return; + } + if (!TDevice.hasInternet()) { + AppContext.showToastShort(R.string.tip_no_internet); + return; + } + + OSChinaApi.pubSoftwareLike(item.getId(), new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + Toast.makeText(mContext, "操作失败...", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.getCode() == 1) { + TweetLikeReverse result = resultBean.getResult(); + boolean like = result.isLiked(); + item.setLiked(like); + int likeCount = item.getLikeCount(); + item.setLikeCount((!item.isLiked() ? likeCount - 1 : likeCount + 1)); + notifyDataSetChanged(); + } else { + Toast.makeText(mContext, "操作失败...", Toast.LENGTH_SHORT).show(); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + }); + + } + + static class SoftwareTweetViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.identityView) + IdentityView mIdentityView; + @Bind(R.id.iv_tweet_face) + PortraitView icon; + @Bind(R.id.tv_tweet_name) + TextView name; + @Bind(R.id.tweet_item) + TweetTextView content; + @Bind(R.id.tv_tweet_time) + TextView pubTime; + @Bind(R.id.tv_tweet_platform) + TextView deviceType; + @Bind(R.id.iv_like_state) + ImageView likeStatus; + @Bind(R.id.tv_tweet_like_count) + TextView likeCount; + @Bind(R.id.tv_tweet_comment_count) + TextView commentCount; + + SoftwareTweetViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/adapter/TopicTweetAdapter.java b/app/src/main/java/net/oschina/app/improve/tweet/adapter/TopicTweetAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..59ba46b5accbf0fbfd84c6886471081418cd005e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/adapter/TopicTweetAdapter.java @@ -0,0 +1,82 @@ +package net.oschina.app.improve.tweet.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.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.tweet.activities.TopicTweetActivity; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * Created by thanatosx on 2016/11/7. + */ + +public class TopicTweetAdapter extends BaseRecyclerAdapter implements View.OnClickListener { + + private static final int[] images = { + R.mipmap.bg_topic_1, R.mipmap.bg_topic_2, R.mipmap.bg_topic_3, + R.mipmap.bg_topic_4, R.mipmap.bg_topic_5 + }; + + public TopicTweetAdapter(Context context) { + super(context, ONLY_FOOTER); + for (int i = 0; i < 10; i++) { + addItem(""); + } + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ViewHolder(LayoutInflater.from(mContext).inflate( + R.layout.list_item_topic_tweet, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder h, Object item, int position) { + ViewHolder holder = (ViewHolder) h; + holder.mViewWallpaper.setImageResource(images[position % 5]); + holder.mLayoutItem1.setVisibility(View.VISIBLE); + holder.mLayoutItem2.setVisibility(View.VISIBLE); + holder.mLayoutItem3.setVisibility(View.VISIBLE); + } + + @Override + public void onClick(View v) { + TopicTweetActivity.show(v.getContext()); + } + + public class ViewHolder extends RecyclerView.ViewHolder { + + @Bind(R.id.iv_wallpaper) + ImageView mViewWallpaper; + @Bind(R.id.layout_item_1) + LinearLayout mLayoutItem1; + @Bind(R.id.layout_item_2) + LinearLayout mLayoutItem2; + @Bind(R.id.layout_item_3) + LinearLayout mLayoutItem3; + @Bind(R.id.layout_bottom) + RelativeLayout mLayoutBottom; + @Bind(R.id.tv_count) + TextView mViewCount; + + @Bind(R.id.layout_wrapper) + LinearLayout mLayoutWrapper; + + public ViewHolder(View view) { + super(view); + ButterKnife.bind(this, view); + mLayoutWrapper.setOnClickListener(TopicTweetAdapter.this); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/adapter/TweetCommentAdapter.java b/app/src/main/java/net/oschina/app/improve/tweet/adapter/TweetCommentAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..404ef2dddd45fbc50d8b712ed0e1f6a9994cddb2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/adapter/TweetCommentAdapter.java @@ -0,0 +1,96 @@ +package net.oschina.app.improve.tweet.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.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.emoji.InputHelper; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.simple.TweetComment; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.UIHelper; +import net.oschina.app.widget.TweetTextView; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * Created by thanatos + * on 16/6/13. + */ +public class TweetCommentAdapter extends BaseRecyclerAdapter { + + private RequestManager reqManager; + private View.OnClickListener onPortraitClickListener; + + public TweetCommentAdapter(Context context) { + super(context, ONLY_FOOTER); + reqManager = Glide.with(context); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new TweetCommentHolderView(LayoutInflater.from(mContext).inflate(R.layout.list_item_tweet_comment, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, TweetComment item, int position) { + TweetCommentHolderView h = (TweetCommentHolderView) holder; + + h.identityView.setup(item.getAuthor()); + h.ivPortrait.setup(item.getAuthor()); + h.ivPortrait.setTag(R.id.iv_tag, item); + h.ivPortrait.setOnClickListener(getOnPortraitClickListener()); + + h.tvName.setText(item.getAuthor().getName()); + h.tvContent.setText(InputHelper.displayEmoji(mContext.getResources(), item.getContent())); + h.tvTime.setText(StringUtils.formatSomeAgo(item.getPubDate())); + } + + private View.OnClickListener getOnPortraitClickListener() { + if (onPortraitClickListener == null) { + onPortraitClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + Object obj = v.getTag(R.id.iv_tag); + if (obj != null && obj instanceof TweetComment) { + TweetComment comment = (TweetComment) obj; + UIHelper.showUserCenter(mContext, comment.getAuthor().getId(), comment.getAuthor().getName()); + } + } + }; + } + return onPortraitClickListener; + } + + public static final class TweetCommentHolderView extends RecyclerView.ViewHolder { + @Bind(R.id.identityView) + public IdentityView identityView; + @Bind(R.id.iv_avatar) + public PortraitView ivPortrait; + @Bind(R.id.tv_name) + public TextView tvName; + @Bind(R.id.tv_pub_date) + public TextView tvTime; + @Bind(R.id.btn_comment) + public ImageView btnReply; + @Bind(R.id.tv_content) + public TweetTextView tvContent; + + public TweetCommentHolderView(View view) { + super(view); + ButterKnife.bind(this, view); + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/adapter/TweetLikeUsersAdapter.java b/app/src/main/java/net/oschina/app/improve/tweet/adapter/TweetLikeUsersAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..204d9ad4ad0b3a1f35c8d16edd0140ed6814240b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/adapter/TweetLikeUsersAdapter.java @@ -0,0 +1,78 @@ +package net.oschina.app.improve.tweet.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.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.simple.TweetLike; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.UIHelper; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * Created by + * thanatos on 16/6/13. + * Updated by + * fei on 17/01/11. + */ +public class TweetLikeUsersAdapter extends BaseRecyclerAdapter { + private View.OnClickListener onPortraitClickListener; + + public TweetLikeUsersAdapter(Context context) { + super(context, ONLY_FOOTER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new LikeUsersHolderView(LayoutInflater.from(mContext).inflate(R.layout.list_cell_tweet_like_user, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, TweetLike item, int position) { + LikeUsersHolderView h = (LikeUsersHolderView) holder; + h.identityView.setup(item.getAuthor()); + h.ivPortrait.setup(item.getAuthor()); + h.ivPortrait.setTag(R.id.iv_tag, item); + h.ivPortrait.setOnClickListener(getOnPortraitClickListener()); + h.tvName.setText(item.getAuthor().getName()); + } + + private View.OnClickListener getOnPortraitClickListener() { + if (onPortraitClickListener == null) { + onPortraitClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + Object object = v.getTag(R.id.iv_tag); + if (object != null && object instanceof TweetLike) { + TweetLike liker = (TweetLike) object; + UIHelper.showUserCenter(mContext, liker.getAuthor().getId(), liker.getAuthor().getName()); + } + } + }; + } + return onPortraitClickListener; + } + + public static final class LikeUsersHolderView extends RecyclerView.ViewHolder { + @Bind(R.id.identityView) + IdentityView identityView; + @Bind(R.id.iv_avatar) + PortraitView ivPortrait; + @Bind(R.id.tv_name) + TextView tvName; + + public LikeUsersHolderView(View view) { + super(view); + ButterKnife.bind(this, view); + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/adapter/TweetQueueAdapter.java b/app/src/main/java/net/oschina/app/improve/tweet/adapter/TweetQueueAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..c7ee22e30ee5c06268e65fe68ec76d2cbefa75b4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/adapter/TweetQueueAdapter.java @@ -0,0 +1,181 @@ +package net.oschina.app.improve.tweet.adapter; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.text.Spannable; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.improve.tweet.service.TweetPublishModel; +import net.oschina.app.improve.utils.parser.TweetParser; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.TLog; +import net.oschina.app.widget.TweetTextView; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Created by JuQiu + * on 16/8/03. + */ +public class TweetQueueAdapter extends RecyclerView.Adapter { + private final List mModels = new ArrayList<>(); + private Callback mCallback; + @SuppressLint("SimpleDateFormat") + private static DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + public TweetQueueAdapter(Callback callback) { + mCallback = callback; + } + + @Override + public Holder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list_tweet_queue, parent, false); + return new Holder(view, new Holder.HolderListener() { + @Override + public void onDelete(TweetPublishModel model) { + remove(model); + Callback callback = mCallback; + if (callback != null) { + callback.onClickDelete(model); + } + } + + @Override + public void onContinue(TweetPublishModel model) { + remove(model); + Callback callback = mCallback; + if (callback != null) { + callback.onClickContinue(model); + } + } + }); + } + + @Override + public void onBindViewHolder(final Holder holder, int position) { + holder.bind(position, mModels.get(position), mCallback.getImageLoader()); + } + + @Override + public int getItemCount() { + return mModels.size(); + } + + public void add(List models) { + TLog.e("TAG", models.size() + ""); + mModels.addAll(models); + notifyDataSetChanged(); + } + + public void remove(TweetPublishModel model) { + int pos = mModels.indexOf(model); + if (pos != -1) { + mModels.remove(pos); + notifyItemRemoved(pos); + } + } + + + public interface Callback { + RequestManager getImageLoader(); + + void onClickContinue(TweetPublishModel model); + + void onClickDelete(TweetPublishModel model); + } + + /** + * Holder + */ + static class Holder extends RecyclerView.ViewHolder implements View.OnLongClickListener { + private TweetTextView mTitle; + private TextView mDate; + private TextView mLog; + private Button mContinue; + private Button mDelete; + private HolderListener mListener; + + private Holder(final View itemView, HolderListener listener) { + super(itemView); + // Add long click + itemView.setOnLongClickListener(this); + + mListener = listener; + + mTitle = (TweetTextView) itemView.findViewById(R.id.tv_title); + mLog = (TextView) itemView.findViewById(R.id.tv_log); + mDate = (TextView) itemView.findViewById(R.id.tv_date); + mContinue = (Button) itemView.findViewById(R.id.btn_continue); + mDelete = (Button) itemView.findViewById(R.id.btn_delete); + + mDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Object obj = Holder.this.itemView.getTag(); + final HolderListener holderListener = mListener; + if (holderListener != null && obj != null && obj instanceof TweetPublishModel) { + holderListener.onDelete((TweetPublishModel) obj); + } + } + }); + mContinue.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Object obj = Holder.this.itemView.getTag(); + final HolderListener holderListener = mListener; + if (holderListener != null && obj != null && obj instanceof TweetPublishModel) { + holderListener.onContinue((TweetPublishModel) obj); + } + } + }); + } + + @SuppressWarnings("unused") + public void bind(int position, TweetPublishModel model, RequestManager loader) { + itemView.setTag(model); + + Context context = itemView.getContext(); + + Spannable spannable = TweetParser.getInstance().parse(context, model.getContent()); + mTitle.setText(spannable); + mTitle.setMovementMethod(LinkMovementMethod.getInstance()); + mTitle.setFocusable(false); + mTitle.setDispatchToParent(true); + mTitle.setLongClickable(false); + + mLog.setText(String.format("Error:%s.", + model.getErrorString() == null ? "null" : model.getErrorString())); + mDate.setText(FORMAT.format(new Date(model.getDate()))); + } + + @Override + public boolean onLongClick(View v) { + TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(mTitle.getText().toString())); + return true; + } + + /** + * Holder 与Adapter之间的桥梁 + */ + interface HolderListener { + void onDelete(TweetPublishModel model); + + void onContinue(TweetPublishModel model); + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/adapter/TweetSelectImageAdapter.java b/app/src/main/java/net/oschina/app/improve/tweet/adapter/TweetSelectImageAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..6b99ea780aacd07bdb3c12b1d32a2e5d310850cf --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/adapter/TweetSelectImageAdapter.java @@ -0,0 +1,302 @@ +package net.oschina.app.improve.tweet.adapter; + +import android.content.Context; +import android.os.Vibrator; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.improve.media.ImageGalleryActivity; +import net.oschina.app.improve.tweet.widget.TweetPicturesPreviewerItemTouchCallback; +import net.oschina.common.utils.CollectionUtil; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by JuQiu + * on 16/7/15. + */ +public class TweetSelectImageAdapter extends RecyclerView.Adapter implements TweetPicturesPreviewerItemTouchCallback.ItemTouchHelperAdapter { + private final int MAX_SIZE = 9; + private final int TYPE_NONE = 0; + private final int TYPE_ADD = 1; + private final List mModels = new ArrayList<>(); + private Callback mCallback; + + public TweetSelectImageAdapter(Callback callback) { + mCallback = callback; + } + + @Override + public int getItemViewType(int position) { + int size = mModels.size(); + if (size >= MAX_SIZE) + return TYPE_NONE; + else if (position == size) { + return TYPE_ADD; + } else { + return TYPE_NONE; + } + } + + @Override + public TweetSelectImageHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list_tweet_publish_selecter, parent, false); + if (viewType == TYPE_NONE) { + return new TweetSelectImageHolder(view, new TweetSelectImageHolder.HolderListener() { + @Override + public void onDelete(Model model) { + Callback callback = mCallback; + if (callback != null) { + int pos = mModels.indexOf(model); + if (pos == -1) + return; + mModels.remove(pos); + if (mModels.size() > 0) + notifyItemRemoved(pos); + else + notifyDataSetChanged(); + } + } + + @Override + public void onDrag(TweetSelectImageHolder holder) { + Callback callback = mCallback; + if (callback != null) { + // Start a drag whenever the handle view it touched + mCallback.onStartDrag(holder); + } + } + + @Override + public void onClick(Model model) { + ImageGalleryActivity.show(mCallback.getContext(), model.path, false); + } + }); + } else { + return new TweetSelectImageHolder(view, new View.OnClickListener() { + @Override + public void onClick(View v) { + Callback callback = mCallback; + if (callback != null) { + callback.onLoadMoreClick(); + } + } + }); + } + } + + @Override + public void onBindViewHolder(final TweetSelectImageHolder holder, int position) { + int size = mModels.size(); + if (size >= MAX_SIZE || size != position) { + Model model = mModels.get(position); + holder.bind(position, model, mCallback.getImgLoader()); + } + } + + @Override + public void onViewRecycled(TweetSelectImageHolder holder) { + Glide.clear(holder.mImage); + } + + @Override + public int getItemCount() { + int size = mModels.size(); + if (size == MAX_SIZE) { + return size; + } else if (size == 0) { + return 0; + } else { + return size + 1; + } + } + + public void clear() { + mModels.clear(); + } + + public void add(Model model) { + if (mModels.size() >= MAX_SIZE) + return; + mModels.add(model); + } + + public void add(String path) { + add(new Model(path)); + } + + public String[] getPaths() { + int size = mModels.size(); + if (size == 0) + return null; + String[] paths = new String[size]; + int i = 0; + for (Model model : mModels) { + paths[i++] = model.path; + } + return paths; + } + + @Override + public boolean onItemMove(int fromPosition, int toPosition) { + //Collections.swap(mModels, fromPosition, toPosition); + if (fromPosition == toPosition) + return false; + + // Move fromPosition to toPosition + CollectionUtil.move(mModels, fromPosition, toPosition); + + notifyItemMoved(fromPosition, toPosition); + return true; + } + + @Override + public void onItemDismiss(int position) { + mModels.remove(position); + notifyItemRemoved(position); + } + + public static class Model { + public Model(String path) { + this.path = path; + } + + public String path; + public boolean isUpload; + } + + public interface Callback { + void onLoadMoreClick(); + + RequestManager getImgLoader(); + + Context getContext(); + + /** + * Called when a view is requesting a start of a drag. + * + * @param viewHolder The holder of the view to drag. + */ + void onStartDrag(RecyclerView.ViewHolder viewHolder); + } + + /** + * TweetSelectImageHolder + */ + static class TweetSelectImageHolder extends RecyclerView.ViewHolder implements TweetPicturesPreviewerItemTouchCallback.ItemTouchHelperViewHolder { + private ImageView mImage; + private ImageView mDelete; + private ImageView mGifMask; + private HolderListener mListener; + + private TweetSelectImageHolder(View itemView, HolderListener listener) { + super(itemView); + mListener = listener; + mImage = (ImageView) itemView.findViewById(R.id.iv_content); + mDelete = (ImageView) itemView.findViewById(R.id.iv_delete); + mGifMask = (ImageView) itemView.findViewById(R.id.iv_is_gif); + + mDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Object obj = v.getTag(); + final HolderListener holderListener = mListener; + if (holderListener != null && obj != null && obj instanceof TweetSelectImageAdapter.Model) { + holderListener.onDelete((TweetSelectImageAdapter.Model) obj); + } + } + }); + mImage.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + final HolderListener holderListener = mListener; + if (holderListener != null) { + holderListener.onDrag(TweetSelectImageHolder.this); + } + return true; + } + }); + mImage.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Object obj = mDelete.getTag(); + final HolderListener holderListener = mListener; + if (holderListener != null && obj != null && obj instanceof TweetSelectImageAdapter.Model) { + holderListener.onClick((TweetSelectImageAdapter.Model) obj); + } + } + }); + mImage.setBackgroundColor(0xffdadada); + } + + private TweetSelectImageHolder(View itemView, View.OnClickListener clickListener) { + super(itemView); + + mImage = (ImageView) itemView.findViewById(R.id.iv_content); + mDelete = (ImageView) itemView.findViewById(R.id.iv_delete); + + mDelete.setVisibility(View.GONE); + mImage.setImageResource(R.mipmap.ic_tweet_add); + mImage.setOnClickListener(clickListener); + mImage.setBackgroundDrawable(null); + } + + public void bind(int position, TweetSelectImageAdapter.Model model, RequestManager loader) { + mDelete.setTag(model); + // In this we need clear before load + Glide.clear(mImage); + // Load image + if (model.path.toLowerCase().endsWith("gif")) { + loader.load(model.path) + .asBitmap() + .centerCrop() + .error(R.mipmap.ic_split_graph) + .into(mImage); + // Show gif mask + mGifMask.setVisibility(View.VISIBLE); + } else { + loader.load(model.path) + .centerCrop() + .error(R.mipmap.ic_split_graph) + .into(mImage); + mGifMask.setVisibility(View.GONE); + } + } + + + @Override + public void onItemSelected() { + try { + Vibrator vibrator = (Vibrator) itemView.getContext().getSystemService(Context.VIBRATOR_SERVICE); + vibrator.vibrate(20); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onItemClear() { + + } + + /** + * Holder 与Adapter之间的桥梁 + */ + interface HolderListener { + void onDelete(TweetSelectImageAdapter.Model model); + + void onDrag(TweetSelectImageHolder holder); + + void onClick(TweetSelectImageAdapter.Model model); + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/contract/TweetDetailContract.java b/app/src/main/java/net/oschina/app/improve/tweet/contract/TweetDetailContract.java new file mode 100644 index 0000000000000000000000000000000000000000..c0a91cfb55c6434a5f3002bb9b5699aa444f29d8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/contract/TweetDetailContract.java @@ -0,0 +1,41 @@ +package net.oschina.app.improve.tweet.contract; + +import net.oschina.app.bean.User; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.simple.TweetComment; + +import java.util.List; + +/** + * Created by thanatosx + * on 16/5/28. + */ + +public interface TweetDetailContract { + + interface Operator { + + Tweet getTweetDetail(); + + void toReply(TweetComment comment); + + void onScroll(); + } + + interface ICmnView { + void onCommentSuccess(TweetComment comment); + + List getComments(); + } + + interface IThumbupView { + void onLikeSuccess(boolean isUp, User user); + } + + interface IAgencyView { + void resetLikeCount(int count); + + void resetCmnCount(int count); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/contract/TweetPublishContract.java b/app/src/main/java/net/oschina/app/improve/tweet/contract/TweetPublishContract.java new file mode 100644 index 0000000000000000000000000000000000000000..67391aed8a327a5f40671f0d7962b55447d4b062 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/contract/TweetPublishContract.java @@ -0,0 +1,49 @@ +package net.oschina.app.improve.tweet.contract; + +import android.content.Context; +import android.os.Bundle; + +import net.oschina.app.improve.bean.simple.About; + +/** + * Created by JuQiu + * on 16/7/14. + */ + +public interface TweetPublishContract { + interface Operator { + void setDataView(View view, String defaultContent, String[] defaultImages, About.Share about,String localImg); + + void publish(); + + void onBack(); + + void loadData(); + + void onSaveInstanceState(Bundle outState); + + void onRestoreInstanceState(Bundle savedInstanceState); + } + + interface View { + Context getContext(); + + String getContent(); + + void setContent(String content, boolean needSelectionEnd); + + void setAbout(About.Share about, boolean needCommit); + + boolean needCommit(); + + String[] getImages(); + + void setImages(String[] paths); + + void finish(); + + Operator getOperator(); + + boolean onBackPressed(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/contract/TweetPublishOperator.java b/app/src/main/java/net/oschina/app/improve/tweet/contract/TweetPublishOperator.java new file mode 100644 index 0000000000000000000000000000000000000000..56c1c5a6934a2f5a30527a2b358ed0e3a3ae1471 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/contract/TweetPublishOperator.java @@ -0,0 +1,225 @@ +package net.oschina.app.improve.tweet.contract; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.support.v4.content.SharedPreferencesCompat; +import android.text.TextUtils; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.tweet.activities.TweetTopicActivity; +import net.oschina.app.improve.tweet.fragments.TweetPublishFragment; +import net.oschina.app.improve.tweet.service.TweetNotificationManager; +import net.oschina.app.improve.tweet.service.TweetPublishService; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; +import net.oschina.common.utils.CollectionUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Created by JuQiu + * on 16/8/22. + */ +public class TweetPublishOperator implements TweetPublishContract.Operator { + private final static String SHARE_FILE_NAME = TweetPublishFragment.class.getName(); + private final static String SHARE_VALUES_CONTENT = "content"; + private final static String SHARE_VALUES_IMAGES = "images"; + private final static String SHARE_VALUES_ABOUT = "about"; + private final static String DEFAULT_PRE = "default"; + private final static String SHARE_LOCAL_IMAGE = "share_image"; + private TweetPublishContract.View mView; + private String mDefaultContent; + private String[] mDefaultImages; + private About.Share mAboutShare; + private String mLocalImg; + + @Override + public void setDataView(TweetPublishContract.View view, String defaultContent, String[] defaultImages, About.Share share, String localImg) { + mView = view; + mDefaultContent = defaultContent; + mDefaultImages = defaultImages; + mAboutShare = share; + mLocalImg = localImg; + } + + @Override + public void publish() { + final Context context = mView.getContext(); + if (context == null) + return; + + if (!TDevice.hasInternet()) { + AppContext.showToastShort(R.string.tip_network_error); + return; + } + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(context); + return; + } + + String content = mView.getContent(); + if (TextUtils.isEmpty(content) || TextUtils.isEmpty(content.trim())) { + AppContext.showToastShort(R.string.tip_content_empty); + return; + } + + if (content.length() > TweetPublishFragment.MAX_TEXT_LENGTH) { + AppContext.showToastShort(R.string.tip_content_too_long); + return; + } + + // Check con't commit to tweet + if (About.check(mAboutShare) && mAboutShare.commitTweetId > 0 && !mView.needCommit()) { + mAboutShare.commitTweetId = 0; + } + + TweetNotificationManager.setup(context); + + final List paths = CollectionUtil.toArrayList(mView.getImages()); + + // To service publish + content = content.replaceAll("[\n\\s]+", " "); + TweetPublishService.startActionPublish(context, content, paths, mAboutShare); + + // Toast + AppContext.showToastShort(R.string.tweet_publishing); + + // Save topic cache + Pattern pattern = Pattern.compile("#.+?#"); + Matcher matcher = pattern.matcher(content); + List topics = new ArrayList<>(); + while (matcher.find()) { + String str = matcher.group().trim() + .replace("#", ""); + topics.add(str); + } + if (topics.size() > 0) { + TweetTopicActivity.saveCache(context, CollectionUtil.toArray(topics, String.class)); + } + + // clear the tweet data + clearAndFinish(context); + } + + @Override + public void onBack() { + saveXmlData(); + mView.finish(); + } + + @Override + public void loadData() { + if (isUseXmlCache()) { + final Context context = mView.getContext(); + SharedPreferences sharedPreferences = context.getSharedPreferences(SHARE_FILE_NAME, Activity.MODE_PRIVATE); + String content = sharedPreferences.getString(SHARE_VALUES_CONTENT, null); + Set set = sharedPreferences.getStringSet(SHARE_VALUES_IMAGES, null); + if (content != null) { + mView.setContent(content, false); + } + if (set != null && set.size() > 0) { + mView.setImages(CollectionUtil.toArray(set, String.class)); + } + } else { + if (!TextUtils.isEmpty(mLocalImg)) { + mView.setImages(new String[]{mLocalImg}); + return; + } + if (mDefaultImages != null && mDefaultImages.length > 0) + mView.setImages(mDefaultImages); + + boolean haveAbout = false; + if (About.check(mAboutShare)) { + mView.setAbout(mAboutShare, mAboutShare.commitTweetId > 0); + haveAbout = true; + } + + if (!TextUtils.isEmpty(mDefaultContent)) + mView.setContent(mDefaultContent, !haveAbout); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + final String content = mView.getContent(); + final String[] paths = mView.getImages(); + if (content != null) + outState.putString(SHARE_VALUES_CONTENT, content); + if (paths != null && paths.length > 0) + outState.putStringArray(SHARE_VALUES_IMAGES, paths); + // save default + if (mDefaultContent != null) { + outState.putString(DEFAULT_PRE + SHARE_VALUES_CONTENT, mDefaultContent); + } + if (mDefaultImages != null && mDefaultImages.length > 0) { + outState.putStringArray(DEFAULT_PRE + SHARE_VALUES_IMAGES, mDefaultImages); + } + if (About.check(mAboutShare)) { + outState.putSerializable(DEFAULT_PRE + SHARE_VALUES_ABOUT, mAboutShare); + } + } + + @Override + public void onRestoreInstanceState(Bundle savedInstanceState) { + String content = savedInstanceState.getString(SHARE_VALUES_CONTENT, null); + String[] images = savedInstanceState.getStringArray(SHARE_VALUES_IMAGES); + if (content != null) { + mView.setContent(content, false); + } + if (images != null && images.length > 0) { + mView.setImages(images); + } + // Read default + mDefaultContent = savedInstanceState.getString(DEFAULT_PRE + SHARE_VALUES_CONTENT, null); + mDefaultImages = savedInstanceState.getStringArray(DEFAULT_PRE + SHARE_VALUES_IMAGES); + mAboutShare = (About.Share) savedInstanceState.getSerializable(DEFAULT_PRE + SHARE_VALUES_ABOUT); + if (About.check(mAboutShare)) + mView.setAbout(mAboutShare, mAboutShare.commitTweetId > 0); + } + + private void clearAndFinish(Context context) { + if (isUseXmlCache()) { + SharedPreferences sharedPreferences = context.getSharedPreferences(SHARE_FILE_NAME, Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(SHARE_VALUES_CONTENT, null); + editor.putStringSet(SHARE_VALUES_IMAGES, null); + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); + } + mView.finish(); + } + + + private void saveXmlData() { + if (isUseXmlCache()) { + final Context context = mView.getContext(); + final String content = mView.getContent(); + final String[] paths = mView.getImages(); + SharedPreferences sharedPreferences = context.getSharedPreferences(SHARE_FILE_NAME, Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(SHARE_VALUES_CONTENT, content); + if (paths != null && paths.length > 0) { + editor.putStringSet(SHARE_VALUES_IMAGES, CollectionUtil.toHashSet(paths)); + } else { + editor.putStringSet(SHARE_VALUES_IMAGES, null); + } + SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); + } + } + + private boolean isUseXmlCache() { + if (!TextUtils.isEmpty(mLocalImg)) + return false; + return TextUtils.isEmpty(mDefaultContent) + && (mDefaultImages == null || mDefaultImages.length == 0) + && (!About.check(mAboutShare)); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/fragments/ListTweetCommentFragment.java b/app/src/main/java/net/oschina/app/improve/tweet/fragments/ListTweetCommentFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..591de59c0a3f8623257c4ad93617467ee359d356 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/fragments/ListTweetCommentFragment.java @@ -0,0 +1,272 @@ +package net.oschina.app.improve.tweet.fragments; + +import android.app.ProgressDialog; +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppConfig; +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.ApiHttpClient; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseRecyclerViewFragment; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.TweetComment; +import net.oschina.app.improve.tweet.adapter.TweetCommentAdapter; +import net.oschina.app.improve.tweet.contract.TweetDetailContract; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.utils.QuickOptionDialogHelper; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.UIHelper; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by thanatos + * on 16/6/13. + */ +public class ListTweetCommentFragment extends BaseRecyclerViewFragment + implements TweetDetailContract.ICmnView, BaseRecyclerAdapter.OnItemLongClickListener { + + private TweetDetailContract.Operator mOperator; + private TweetDetailContract.IAgencyView mAgencyView; + private int mDeleteIndex = 0; + + public static ListTweetCommentFragment instantiate(TweetDetailContract.Operator operator, TweetDetailContract.IAgencyView mAgencyView) { + ListTweetCommentFragment fragment = new ListTweetCommentFragment(); + fragment.mOperator = operator; + fragment.mAgencyView = mAgencyView; + return fragment; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mOperator = (TweetDetailContract.Operator) context; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + mOperator.onScroll(); + } + } + }); + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + TweetCommentAdapter adapter = new TweetCommentAdapter(getContext()); + adapter.setOnItemClickListener(this); + adapter.setOnItemLongClickListener(this); + return adapter; + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } + + @Override + protected void onRequestSuccess(int code) { + super.onRequestSuccess(code); + if (mAdapter.getCount() < 20 && mAgencyView != null) + mAgencyView.resetCmnCount(mAdapter.getCount()); + } + + @Override + public void requestData() { + String token = isRefreshing ? null : mBean.getNextPageToken(); + OSChinaApi.getTweetCommentList(mOperator.getTweetDetail().getId(), token, mHandler); + } + + @Override + protected boolean isNeedCache() { + return false; + } + + @Override + protected boolean isNeedEmptyView() { + return false; + } + + @Override + public void onItemClick(int position, long itemId) { + super.onItemClick(position, itemId); + TweetComment item = mAdapter.getItem(position); + if (item != null) + mOperator.toReply(item); + } + + @Override + public void onLongClick(int position, long itemId) { + final TweetComment comment = mAdapter.getItem(position); + if (comment == null) return; + mDeleteIndex = position; + + QuickOptionDialogHelper.with(getContext()) + .addCopy(HTMLUtil.delHTMLTag(comment.getContent())) + .addOther(comment.getAuthor().getId() == AccountHelper.getUserId(), + R.string.delete, new Runnable() { + @Override + public void run() { + handleDeleteComment(comment); + } + }).show(); + + } + + private void handleDeleteComment(TweetComment comment) { + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(getActivity()); + return; + } + OSChinaApi.deleteTweetComment(mOperator.getTweetDetail().getId(), comment.getId(), new TextHttpResponseHandler() { + private ProgressDialog dialog = DialogHelper.getProgressDialog(getContext(), "正在删除……", false); + + @Override + public void onStart() { + super.onStart(); + dialog.show(); + } + + @Override + public void onFinish() { + super.onFinish(); + dialog.dismiss(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + AppContext.showToastShort("删除失败"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + ResultBean result = AppOperator.createGson().fromJson( + responseString, new TypeToken() { + }.getType()); + if (result.isSuccess()) { + mAdapter.removeItem(mDeleteIndex); + int count = mOperator.getTweetDetail().getCommentCount() - 1; + mOperator.getTweetDetail().setCommentCount(count); + mAgencyView.resetCmnCount(count); + AppContext.showToastShort("删除成功"); + } else { + AppContext.showToastShort("删除失败"); + } + } + }); + } + + @Override + public void onCommentSuccess(TweetComment comment) { + if (mContext == null || mRefreshLayout == null) { + return; + } + if (mRefreshLayout.isLoding()) { + ApiHttpClient.cancelALL(); + } + isRefreshing = true; + mRefreshLayout.post(new Runnable() { + @Override + public void run() { + mRefreshLayout.setRefreshing(true); + } + }); + mRefreshLayout.setOnLoading(true); + mAdapter.setState(BaseRecyclerAdapter.STATE_HIDE, true); + OSChinaApi.getTweetCommentList(mOperator.getTweetDetail().getId(), null, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (mContext == null) + return; + onRequestError(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (mContext == null) + return; + try { + ResultBean> resultBean = AppOperator.createGson().fromJson(responseString, getType()); + if (resultBean != null && resultBean.isSuccess() && resultBean.getResult().getItems() != null) { + showRefreshSuccess(resultBean); + onRequestSuccess(resultBean.getCode()); + } else { + if (resultBean != null && resultBean.getCode() == ResultBean.RESULT_TOKEN_ERROR) { + SimplexToast.show(getActivity(), resultBean.getMessage()); + } + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + + @Override + public void onFinish() { + super.onFinish(); + if (mContext == null) + return; + onRequestFinish(); + } + + @Override + public void onCancel() { + super.onCancel(); + if (mContext == null) + return; + onRequestFinish(); + } + }); + } + + private void showRefreshSuccess(ResultBean> resultBean) { + mBean.setNextPageToken(resultBean.getResult().getNextPageToken()); + AppConfig.getAppConfig(getActivity()).set("system_time", resultBean.getTime()); + mBean.setItems(resultBean.getResult().getItems()); + mAdapter.clear(); + mAdapter.addAll(mBean.getItems()); + mBean.setPrevPageToken(resultBean.getResult().getPrevPageToken()); + mRefreshLayout.setCanLoadMore(true); + if (resultBean.getResult().getItems() == null + || resultBean.getResult().getItems().size() < 20) + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + if (mAdapter.getItems().size() > 0) { + mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + mRefreshLayout.setVisibility(View.VISIBLE); + mRecyclerView.setVisibility(View.VISIBLE); + } else { + mErrorLayout.setErrorType( + isNeedEmptyView() + ? EmptyLayout.NODATA + : EmptyLayout.HIDE_LAYOUT); + } + } + + @Override + public List getComments() { + return mAdapter.getItems(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/fragments/ListTweetLikeUsersFragment.java b/app/src/main/java/net/oschina/app/improve/tweet/fragments/ListTweetLikeUsersFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..7eb74a0fc3c46703ecfa6edb54b6505477313543 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/fragments/ListTweetLikeUsersFragment.java @@ -0,0 +1,105 @@ +package net.oschina.app.improve.tweet.fragments; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.bean.User; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseRecyclerViewFragment; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.TweetLike; +import net.oschina.app.improve.tweet.adapter.TweetLikeUsersAdapter; +import net.oschina.app.improve.tweet.contract.TweetDetailContract; +import net.oschina.app.util.UIHelper; + +import java.lang.reflect.Type; + +/** + * 动弹详情, 点赞列表 + * Created by thanatos + * on 16/6/13. + */ +public class ListTweetLikeUsersFragment extends BaseRecyclerViewFragment implements TweetDetailContract.IThumbupView { + + private TweetDetailContract.Operator mOperator; + private TweetDetailContract.IAgencyView mAgencyView; + + public static ListTweetLikeUsersFragment instantiate(TweetDetailContract.Operator operator, TweetDetailContract.IAgencyView mAgencyView) { + ListTweetLikeUsersFragment fragment = new ListTweetLikeUsersFragment(); + fragment.mOperator = operator; + fragment.mAgencyView = mAgencyView; + return fragment; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mOperator = (TweetDetailContract.Operator) context; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + mOperator.onScroll(); + } + } + }); + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + return new TweetLikeUsersAdapter(getContext()); + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } + + @Override + protected boolean isNeedCache() { + return false; + } + + @Override + protected boolean isNeedEmptyView() { + return false; + } + + @Override + protected void requestData() { + String token = isRefreshing || mBean == null ? null : mBean.getNextPageToken(); + OSChinaApi.getTweetLikeList(mOperator.getTweetDetail().getId(), token, mHandler); + } + + @Override + protected void onRequestSuccess(int code) { + super.onRequestSuccess(code); + if (mAdapter.getCount() < 20 && mAgencyView != null) + mAgencyView.resetLikeCount(mAdapter.getCount()); + } + + @Override + public void onItemClick(int position, long itemId) { + super.onItemClick(position, itemId); + TweetLike liker = mAdapter.getItem(position); + if (liker == null) return; + UIHelper.showUserCenter(getContext(), liker.getAuthor().getId(), liker.getAuthor().getName()); + } + + @Override + public void onLikeSuccess(boolean isUp, User user) { + onRefreshing(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/fragments/TopicTweetFragment.java b/app/src/main/java/net/oschina/app/improve/tweet/fragments/TopicTweetFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..4f82dc522c877ace3b9f6edc97cf0c74c8df7e25 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/fragments/TopicTweetFragment.java @@ -0,0 +1,46 @@ +package net.oschina.app.improve.tweet.fragments; + +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseGeneralRecyclerFragment; +import net.oschina.app.improve.tweet.adapter.TopicTweetAdapter; + +import java.lang.reflect.Type; + +/** + * Created by thanatosx on 2016/11/7. + */ + +public class TopicTweetFragment extends BaseGeneralRecyclerFragment { + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + return new TopicTweetAdapter(getContext()); + } + + @Override + protected Type getType() { + return null; + } + + @Override + public void initData() { + super.initData(); + mAdapter.setState(BaseRecyclerAdapter.STATE_LOAD_MORE, true); + mRefreshLayout.setRefreshing(false); + } + + @Override + protected boolean isNeedEmptyView() { + return false; + } + + @Override + public void onRefreshing() { + + } + + @Override + protected boolean isNeedCache() { + return false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/fragments/TweetDetailViewPagerFragment.java b/app/src/main/java/net/oschina/app/improve/tweet/fragments/TweetDetailViewPagerFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..0df28cdd4c52f94807f7da85000e47be6ae1ea4c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/fragments/TweetDetailViewPagerFragment.java @@ -0,0 +1,173 @@ +package net.oschina.app.improve.tweet.fragments; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.view.ViewPager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import net.oschina.app.R; +import net.oschina.app.bean.User; +import net.oschina.app.improve.bean.simple.TweetComment; +import net.oschina.app.improve.main.tweet.comment.TweetCommentFragment; +import net.oschina.app.improve.tweet.contract.TweetDetailContract; + +import java.util.List; + +/** + * 赞 | 评论 + * Created by thanatos on 16/6/12. + *

    + * TweetDetailActivity TweetDetailViewPagerFragment ListTweetCommentFragment ListTweetLikeUsersFragment + * | | | | + * | on comment successful | on comment successful | | + * | ----------------------> | -------------------------->| | + * | | | | + * | on admire successful | on admire successful | + * | ----------------------> | -----------------------------------------------------> | + * | | | | + * | to reset comment count | to reset comment count | | + * | ----------------------> | <------------------------- | | + * | | | | + * | to admire comment count | to admire comment count | + * | ----------------------> | <-------------------------------------------------------| + * | | | | + * | onScroll, getTweetDetail... | | + * | <----------------------------------------------------| | + * | | | | + * | onScroll | + * |<----------------------------------------------------------------------------------| + */ +public class TweetDetailViewPagerFragment extends Fragment + implements TweetDetailContract.ICmnView, TweetDetailContract.IThumbupView, TweetDetailContract.IAgencyView { + + private ViewPager mViewPager; + private TabLayout mTabLayout; + protected FragmentStatePagerAdapter mAdapter; + private TweetDetailContract.ICmnView mCmnViewImp; + private TweetDetailContract.IThumbupView mThumbupViewImp; + private TweetDetailContract.Operator mOperator; + + public static TweetDetailViewPagerFragment instantiate() { + return new TweetDetailViewPagerFragment(); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mOperator = (TweetDetailContract.Operator) context; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_tweet_view_pager, container, false); + mViewPager = (ViewPager) view.findViewById(R.id.view_pager); + mTabLayout = (TabLayout) view.findViewById(R.id.tab_nav); + return view; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + if (mAdapter == null) { + final ListTweetLikeUsersFragment mCmnFrag; + mThumbupViewImp = mCmnFrag = ListTweetLikeUsersFragment.instantiate(mOperator, this); + + final TweetCommentFragment mThumbupFrag; + mCmnViewImp = mThumbupFrag = TweetCommentFragment.instantiate(mOperator, this); + + mViewPager.setAdapter(mAdapter = new FragmentStatePagerAdapter(getChildFragmentManager()) { + @Override + public Fragment getItem(int position) { + switch (position) { + case 0: + return mCmnFrag; + + case 1: + return mThumbupFrag; + + } + return null; + } + + @Override + public int getCount() { + return 2; + } + + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case 0: + return String.format("赞 (%s)", mOperator.getTweetDetail().getLikeCount()); + case 1: + return String.format("评论 (%s)", mOperator.getTweetDetail().getCommentCount()); + } + return null; + } + }); + mTabLayout.setupWithViewPager(mViewPager); + mViewPager.setCurrentItem(1); + } else { + mViewPager.setAdapter(mAdapter); + } + } + + @Override + public void onCommentSuccess(TweetComment comment) { + mOperator.getTweetDetail().setCommentCount(mOperator.getTweetDetail().getCommentCount() + 1); // Bean的事,真不是我想这样干 + if (mCmnViewImp != null) mCmnViewImp.onCommentSuccess(comment); + TabLayout.Tab tab = mTabLayout.getTabAt(1); + if (tab != null) + tab.setText(String.format("评论 (%s)", mOperator.getTweetDetail().getCommentCount())); + } + + @Override + public void onLikeSuccess(boolean isUp, User user) { + mOperator.getTweetDetail().setLikeCount(mOperator.getTweetDetail().getLikeCount() + (isUp ? 1 : -1)); + if (mThumbupViewImp != null) mThumbupViewImp.onLikeSuccess(isUp, user); + TabLayout.Tab tab = mTabLayout.getTabAt(0); + if (tab != null) + tab.setText(String.format("赞 (%s)", mOperator.getTweetDetail().getLikeCount())); + } + + @Override + public void resetLikeCount(int count) { + mOperator.getTweetDetail().setLikeCount(count); + TabLayout.Tab tab = mTabLayout.getTabAt(0); + if (tab != null) tab.setText(String.format("赞 (%s)", count)); + } + + @Override + public void resetCmnCount(int count) { + mOperator.getTweetDetail().setCommentCount(count); + TabLayout.Tab tab = mTabLayout.getTabAt(1); + if (tab != null) tab.setText(String.format("评论 (%s)", count)); + } + + @Override + public List getComments() { + if (mCmnViewImp == null) + return null; + return mCmnViewImp.getComments(); + } + + public TweetDetailContract.ICmnView getCommentViewHandler() { + return this; + } + + public TweetDetailContract.IThumbupView getThumbupViewHandler() { + return this; + } + + public TweetDetailContract.IAgencyView getAgencyViewHandler() { + return this; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/fragments/TweetFragment.java b/app/src/main/java/net/oschina/app/improve/tweet/fragments/TweetFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..84583fa85d8e3c79448f3b5f88295bf788c4bdbe --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/fragments/TweetFragment.java @@ -0,0 +1,625 @@ +package net.oschina.app.improve.tweet.fragments; + +import android.app.ProgressDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppConfig; +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.bean.Constants; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.account.base.AccountBaseActivity; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseGeneralRecyclerFragment; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.tweet.activities.TweetDetailActivity; +import net.oschina.app.improve.tweet.activities.TweetPublishActivity; +import net.oschina.app.improve.tweet.service.TweetNotificationManager; +import net.oschina.app.improve.tweet.service.TweetPublishService; +import net.oschina.app.improve.user.adapter.UserTweetAdapter; +import net.oschina.app.improve.utils.CacheManager; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.utils.parser.TweetParser; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.util.HTMLUtil; +import net.oschina.app.util.TDevice; + +import org.json.JSONObject; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; +import cz.msebera.android.httpclient.Header; + + +/** + * 动弹列表 + * Created by huanghaibin_dev + * Updated by thanatosx + * on 2016/7/18. + * Updated by jzz + * on 2017/02/15 + */ +public class TweetFragment extends BaseGeneralRecyclerFragment + implements BaseRecyclerAdapter.OnItemLongClickListener, TweetNotificationManager.TweetPubNotify { + + public static final int CATALOG_NEW = 0X0001; + public static final int CATALOG_HOT = 0X0002; + public static final int CATALOG_MYSELF = 0X0003; + public static final int CATALOG_FRIENDS = 0X0004; + public static final int CATALOG_TAG = 0X0005; + public static final int CATALOG_SOMEONE = 0X0006; + + public static final String CACHE_NEW_TWEET = "cache_new_tweet"; + public static final String CACHE_HOT_TWEET = "cache_hot_tweet"; + public static final String CACHE_USER_TWEET = "cache_user_tweet"; + public static final String CACHE_USER_FRIEND = "cache_user_friend"; + + public static final String BUNDLE_KEY_USER_ID = "BUNDLE_KEY_USER_ID"; + public static final String BUNDLE_KEY_TAG = "BUNDLE_KEY_LOGIN_USER_TAG"; + public static final String BUNDLE_KEY_REQUEST_CATALOG = "BUNDLE_KEY_REQUEST_CATALOG"; + + public int mReqCatalog;//请求类型 + public long mUserId; // login user or another user + public String tag; + private LoginReceiver mReceiver; + + @Bind(R.id.lay_notification) + LinearLayout mLayNotification; + @Bind(R.id.bt_ignore) + Button mBtIgnore; + @Bind(R.id.bt_retry) + Button mBtRetry; + @Bind(R.id.notification_baseline) + View mBaseLine; + + private String[] mPubFailedCacheIds; + private boolean isShowIdentityView; + + public static Fragment instantiate(long uid) { + Bundle bundle = new Bundle(); + bundle.putLong(BUNDLE_KEY_USER_ID, uid); + bundle.putInt(BUNDLE_KEY_REQUEST_CATALOG, CATALOG_MYSELF); + Fragment fragment = new TweetFragment(); + fragment.setArguments(bundle); + return fragment; + } + + /** + * @param uid user id + * @param code 只是为了让方法指纹不一样而已,哈哈 + * @return {@link Fragment} + */ + @SuppressWarnings("unused") + public static Fragment instantiate(long uid, int code, boolean isShowIdentityView) { + Bundle bundle = new Bundle(); + bundle.putLong(BUNDLE_KEY_USER_ID, uid); + bundle.putBoolean("isShowIdentityView", isShowIdentityView); + bundle.putInt(BUNDLE_KEY_REQUEST_CATALOG, CATALOG_SOMEONE); + Fragment fragment = new TweetFragment(); + fragment.setArguments(bundle); + return fragment; + } + + public static Fragment instantiate(String tag) { + Bundle bundle = new Bundle(); + bundle.putString(BUNDLE_KEY_TAG, tag); + bundle.putInt(BUNDLE_KEY_REQUEST_CATALOG, CATALOG_TAG); + Fragment fragment = new TweetFragment(); + fragment.setArguments(bundle); + return fragment; + } + + public static Fragment instantiate(int catalog) { + Bundle bundle = new Bundle(); + bundle.putInt(BUNDLE_KEY_REQUEST_CATALOG, catalog); + Fragment fragment = new TweetFragment(); + fragment.setArguments(bundle); + return fragment; + } + + public static Fragment newInstance(int catalog) { + Bundle bundle = new Bundle(); + bundle.putInt(BUNDLE_KEY_REQUEST_CATALOG, catalog); + Fragment fragment = new TweetFragment(); + fragment.setArguments(bundle); + return fragment; + } + + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + isShowIdentityView = bundle.getBoolean("isShowIdentityView", true); + mReqCatalog = bundle.getInt(BUNDLE_KEY_REQUEST_CATALOG, CATALOG_NEW); + switch (mReqCatalog) { + case CATALOG_FRIENDS: + case CATALOG_SOMEONE: + case CATALOG_MYSELF: + mUserId = bundle.getLong(BUNDLE_KEY_USER_ID, AccountHelper.getUserId()); + break; + case CATALOG_TAG: + tag = bundle.getString(BUNDLE_KEY_TAG); + setHasOptionsMenu(true); + break; + } + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mRecyclerView.setItemAnimator(null); + } + + /** + * fragment被销毁的时候重新调用,初始化保存的数据 + * + * @param bundle onSaveInstanceState + */ + @Override + protected void onRestartInstance(Bundle bundle) { + super.onRestartInstance(bundle); + mReqCatalog = bundle.getInt(BUNDLE_KEY_REQUEST_CATALOG, CATALOG_NEW); + mUserId = bundle.getLong(BUNDLE_KEY_USER_ID, AccountHelper.getUserId()); + } + + @Override + public void onResume() { + super.onResume(); + if (mReqCatalog == 1) { + //每次进入或回到最新动弹界面先查询是否有本地发送失败的动弹缓存 + if (AccountHelper.isLogin()) { + TweetPublishService.startActionSearchFailed(AppContext.context()); + } else { + showDraftsBox(View.GONE); + } + } + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + TweetNotificationManager.setup(getContext()); + } + + @Override + public void initData() { + switch (mReqCatalog) { + case CATALOG_NEW: + CACHE_NAME = CACHE_NEW_TWEET; + break; + case CATALOG_HOT: + CACHE_NAME = CACHE_HOT_TWEET; + break; + case CATALOG_MYSELF: + case CATALOG_FRIENDS: + CACHE_NAME = mReqCatalog == CATALOG_MYSELF ? CACHE_USER_TWEET : CACHE_USER_FRIEND; + if (mReceiver == null) { + mReceiver = new LoginReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(AccountBaseActivity.ACTION_ACCOUNT_FINISH_ALL); + filter.addAction(Constants.INTENT_ACTION_LOGOUT); + LocalBroadcastManager.getInstance(getContext()).registerReceiver(mReceiver, filter); + } + break; + default: + CACHE_NAME = null; + } + + super.initData(); + + mAdapter.setOnItemLongClickListener(this); + // 某用户的动弹 or 登录用户的好友动弹 + if (mUserId == 0 && mReqCatalog == CATALOG_MYSELF || + (!AccountHelper.isLogin() && mReqCatalog == CATALOG_FRIENDS)) { + mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); + mErrorLayout.setErrorMessage("未登录"); + } + + + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + TweetNotificationManager.bindNotify(getContext().getApplicationContext(), this); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + TweetNotificationManager.unBoundNotify(this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mReceiver != null) { + LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mReceiver); + } + } + + @Override + public void onLongClick(final int position, long itemId) { + final Tweet tweet = mAdapter.getItem(position); + if (tweet == null) return; + + List operators = new ArrayList<>(); + operators.add(getString(R.string.copy)); + if (AccountHelper.getUserId() == (int) tweet.getAuthor().getId()) { + operators.add(getString(R.string.delete)); + } + operators.add(getString(R.string.transmit)); + + final String[] os = new String[operators.size()]; + operators.toArray(os); + + DialogHelper.getSelectDialog(getContext(), os, getString(R.string.cancel), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int index) { + switch (index) { + case 0: + TDevice.copyTextToBoard(HTMLUtil.delHTMLTag(tweet.getContent())); + break; + case 1: + if (os.length != 2) { + DialogHelper.getConfirmDialog(getActivity(), "是否删除该动弹?", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + OSChinaApi.deleteTweet(tweet.getId(), new DeleteHandler(position)); + } + }).show(); + break; + } + case 2: + String content = null; + About.Share share; + if (tweet.getAbout() == null) { + share = About.buildShare(tweet.getId(), OSChinaApi.CATALOG_TWEET); + share.title = tweet.getAuthor().getName(); + share.content = tweet.getContent(); + } else { + share = About.buildShare(tweet.getAbout()); + content = "//@" + tweet.getAuthor().getName() + " :" + tweet.getContent(); + content = TweetParser.getInstance().clearHtmlTag(content).toString(); + } + share.commitTweetId = tweet.getId(); + share.fromTweetId = tweet.getId(); + TweetPublishActivity.show(getContext(), null, content, share); + break; + } + } + }).show(); + } + + private class LoginReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (AccountHelper.isLogin()) { + mUserId = AccountHelper.getUserId(); + mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + onRefreshing(); + } else { + mUserId = 0; + mErrorLayout.setErrorType(EmptyLayout.NETWORK_ERROR); + mErrorLayout.setErrorMessage("未登录"); + } + } + } + + @Override + protected void requestData() { + super.requestData(); + String pageToken = isRefreshing ? null : mBean.getNextPageToken(); + switch (mReqCatalog) { + case CATALOG_NEW: + //注册草稿箱查询操作 + TweetPublishService.startActionSearchFailed(AppContext.context()); + OSChinaApi.getTweetList(null, null, 1, 1, pageToken, mHandler); + break; + case CATALOG_HOT: + OSChinaApi.getTweetList(null, null, 1, 2, pageToken, mHandler); + break; + case CATALOG_SOMEONE: + case CATALOG_MYSELF: + if (mUserId <= 0) break; + OSChinaApi.getTweetList(mUserId, null, null, 1, pageToken, mHandler); + break; + case CATALOG_FRIENDS: + OSChinaApi.getTweetList(null, null, 2, 1, pageToken, mHandler); + break; + case CATALOG_TAG: + OSChinaApi.getTweetList(null, tag, null, 1, pageToken, mHandler); + break; + } + } + + @Override + protected boolean isNeedEmptyView() { + return mReqCatalog != CATALOG_TAG && mReqCatalog != CATALOG_SOMEONE; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + if (mReqCatalog == CATALOG_TAG) { + inflater.inflate(R.menu.pub_topic_menu, menu); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.public_menu_send: + TweetPublishActivity.show(getContext(), null, "#" + tag + "#"); + break; + } + return super.onOptionsItemSelected(item); + } + + @SuppressWarnings("unchecked") + @Override + public void onItemClick(int position, long itemId) { + Tweet tweet = mAdapter.getItem(position); + if (tweet == null) return; + TweetDetailActivity.show(getContext(), tweet); + } + + /** + * 未登录时显示的图标,点击应该跳到的登录界面 + * + * @param v {@link View} + */ + @OnClick({R.id.bt_ignore, R.id.bt_retry}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.bt_ignore: + if (AccountHelper.isLogin()) { + //忽略该动弹 + showDraftsBox(View.GONE); + AppContext.showToastShort(R.string.tweet_ignore_hint); + if (mPubFailedCacheIds == null) return; + for (String id : mPubFailedCacheIds) { + if (TextUtils.isEmpty(id)) continue; + TweetPublishService.startActionDelete(getContext(), id); + } + } else { + LoginActivity.show(this, 1); + } + break; + case R.id.bt_retry: + //重新发送动弹 + + if (!AccountHelper.isLogin()) { + LoginActivity.show(this, 1); + return; + } + + if (!TDevice.hasInternet()) { + AppContext.showToastShort(R.string.tip_network_error); + return; + } + + AppContext.showToastShort(R.string.tweet_retry_publishing_hint); + + showDraftsBox(View.GONE); + if (mPubFailedCacheIds == null) return; + for (String id : mPubFailedCacheIds) { + if (TextUtils.isEmpty(id)) continue; + TweetPublishService.startActionContinue(getContext(), id); + } + break; + default: + if ((mReqCatalog == CATALOG_MYSELF || mReqCatalog == CATALOG_FRIENDS) + && !AccountHelper.isLogin()) { + LoginActivity.show(this, 1); + } else { + super.onClick(v); + } + break; + } + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + UserTweetAdapter adapter = new UserTweetAdapter(this); + adapter.setShowIdentityView(isShowIdentityView); + return adapter; + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } + + @Override + protected Class getCacheClass() { + return Tweet.class; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putInt(BUNDLE_KEY_REQUEST_CATALOG, mReqCatalog); + outState.putLong(BUNDLE_KEY_USER_ID, mUserId); + super.onSaveInstanceState(outState); + } + + @SuppressWarnings("unchecked") + @Override + protected void setListData(ResultBean> resultBean) { + if (resultBean != null) { + final PageBean pageBean = resultBean.getResult(); + if (pageBean != null) { + final List items = pageBean.getItems(); + final boolean isEmpty = items == null || items.size() == 0; + if (!isEmpty) + mBean.setNextPageToken(pageBean.getNextPageToken()); + + if (isRefreshing) { + AppConfig.getAppConfig(getActivity()).set("system_time", resultBean.getTime()); + mAdapter.clear(); + ((BaseGeneralRecyclerAdapter) mAdapter).clearPreItems(); + ((BaseGeneralRecyclerAdapter) mAdapter).addItems(items); + + mBean.setItems(items); + mBean.setPrevPageToken(pageBean.getPrevPageToken()); + mRefreshLayout.setCanLoadMore(true); + if (isNeedCache()) { + CacheManager.saveToJson(getActivity(), CACHE_NAME, items); + } + } else { + ((BaseGeneralRecyclerAdapter) mAdapter).addItems(items); + } + + if (isEmpty) { + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + mRefreshLayout.setCanLoadMore(false); + } + } + } + + if (mAdapter.getItems().size() > 0) { + mErrorLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + mRefreshLayout.setVisibility(View.VISIBLE); + mRecyclerView.setVisibility(View.VISIBLE); + } else { + mErrorLayout.setErrorType(isNeedEmptyView() ? EmptyLayout.NODATA : EmptyLayout.HIDE_LAYOUT); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == AppCompatActivity.RESULT_OK && requestCode == 1) { + mErrorLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + mUserId = AccountHelper.getUserId(); + onRefreshing(); + } + } + + @Override + public void onTweetPubSuccess() { + //如果再次发送动弹成功,gone草稿箱view + showDraftsBox(View.GONE); + } + + @Override + public void onTweetPubFailed() { + //发送动弹失败,显示草稿箱view + showDraftsBox(View.VISIBLE); + } + + @Override + public void onTweetPubProgress(String progressContent) { + } + + @Override + public void onTweetPubContinue() { + + } + + @Override + public void onTweetPubDelete() { + //忽略该条动弹之后,直接gone草稿箱view + showDraftsBox(View.GONE); + } + + @Override + public void pnTweetReceiverSearchFailed(String[] pubFailedCacheIds) { + this.mPubFailedCacheIds = pubFailedCacheIds; + if (mReqCatalog == CATALOG_NEW && pubFailedCacheIds != null && pubFailedCacheIds.length > 0) { + showDraftsBox(View.VISIBLE); + } else { + showDraftsBox(View.GONE); + } + } + + private class DeleteHandler extends TextHttpResponseHandler { + private int position; + private ProgressDialog dialog; + + DeleteHandler(int position) { + this.position = position; + this.dialog = DialogHelper.getProgressDialog(getContext(), "正在删除……", false); + } + + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + SimplexToast.show(getContext(), "删除失败"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + JSONObject jsonObject = new JSONObject(responseString); + if (jsonObject.optInt("code") == 1) { + mAdapter.removeItem(position); + Toast.makeText(getActivity(), "删除成功", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getActivity(), jsonObject.optString("message"), Toast.LENGTH_SHORT).show(); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + + @Override + public void onStart() { + super.onStart(); + dialog.show(); + } + + @Override + public void onFinish() { + super.onFinish(); + dialog.dismiss(); + } + } + + private void showDraftsBox(int GoneOrVisible) { + if (mLayNotification != null) + mLayNotification.setVisibility(GoneOrVisible); + if (mBtIgnore != null) + mBtIgnore.setVisibility(GoneOrVisible); + if (mBtRetry != null) + mBtRetry.setVisibility(GoneOrVisible); + if (mBaseLine != null) + mBaseLine.setVisibility(GoneOrVisible); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/fragments/TweetPublishFragment.java b/app/src/main/java/net/oschina/app/improve/tweet/fragments/TweetPublishFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..b8366aae22c3ead4b422aa26bbc55a3f0d7c95cf --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/fragments/TweetPublishFragment.java @@ -0,0 +1,456 @@ +package net.oschina.app.improve.tweet.fragments; + + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.view.ViewCompat; +import android.text.Editable; +import android.text.Spannable; +import android.text.TextUtils; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.emoji.Emojicon; +import net.oschina.app.emoji.InputHelper; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.base.activities.BaseBackActivity; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.face.FacePanelView; +import net.oschina.app.improve.tweet.activities.TweetTopicActivity; +import net.oschina.app.improve.tweet.contract.TweetPublishContract; +import net.oschina.app.improve.tweet.contract.TweetPublishOperator; +import net.oschina.app.improve.tweet.widget.TweetPicturesPreviewer; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; +import net.oschina.app.improve.utils.parser.TweetParser; +import net.oschina.app.improve.widget.RichEditText; +import net.oschina.app.improve.widget.adapter.OnKeyArrivedListenerAdapterV2; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; +import net.oschina.common.adapter.TextWatcherAdapter; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 发布动弹界面实现 + */ +@SuppressWarnings("WeakerAccess") +public class TweetPublishFragment extends BaseFragment implements View.OnClickListener, + TweetPublishContract.View { + + public static final int MAX_TEXT_LENGTH = 160; + public static final int REQUEST_CODE_SELECT_FRIENDS = 0x0001; + public static final int REQUEST_CODE_SELECT_TOPIC = 0x0002; + + @Bind(R.id.edit_content) + RichEditText mEditContent; + + @Bind(R.id.recycler_images) + TweetPicturesPreviewer mLayImages; + + @Bind(R.id.txt_indicator) + TextView mIndicator; + + @Bind(R.id.icon_back) + View mIconBack; + + @Bind(R.id.icon_send) + View mIconSend; + + private TweetPublishContract.Operator mOperator; + private FacePanelView mFacePanel; + + public TweetPublishFragment() { + // Required empty public constructor + } + + @Override + public void onAttach(Context context) { + // init operator + this.mOperator = new TweetPublishOperator(); + String defaultContent = null; + String[] paths = null; + About.Share share = null; + Bundle bundle = getArguments(); + String localImg = null; + if (bundle != null) { + defaultContent = bundle.getString("defaultContent"); + paths = bundle.getStringArray("defaultImages"); + share = (About.Share) bundle.getSerializable("aboutShare"); + localImg = bundle.getString("imageUrl"); + } + this.mOperator.setDataView(this, defaultContent, paths, share,localImg); + + super.onAttach(context); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_tweet_publish; + } + + @SuppressLint("ClickableViewAccessibility") + @SuppressWarnings("all") + @Override + protected void initWidget(View root) { + super.initWidget(root); + + // init face panel + mFacePanel = findView(R.id.panel_face); + mFacePanel.setListener(new FacePanelView.FacePanelListener() { + @Override + public void onDeleteClick() { + InputHelper.backspace(mEditContent); + } + + @Override + public void hideSoftKeyboard() { + TweetPublishFragment.this.hideSoftKeyboard(); + } + + @Override + public void onFaceClick(Emojicon v) { + InputHelper.input2OSC(mEditContent, v); + } + }); + + // set hide action + + mLayImages.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + hideAllKeyBoard(); + return false; + } + }); + + // add text change listener + mEditContent.addTextChangedListener(new TextWatcherAdapter() { + @Override + public void afterTextChanged(Editable s) { + final int len = s.length(); + final int surplusLen = MAX_TEXT_LENGTH - len; + // set the send icon state + setSendIconStatus(len > 0 && surplusLen >= 0, s.toString()); + // checkShare the indicator state + if (surplusLen > 10) { + // hide + if (mIndicator.getVisibility() != View.INVISIBLE) { + ViewCompat.animate(mIndicator) + .alpha(0) + .setDuration(200) + .withEndAction(new Runnable() { + @Override + public void run() { + mIndicator.setVisibility(View.INVISIBLE); + } + }) + .start(); + } + } else { + // show + if (mIndicator.getVisibility() != View.VISIBLE) { + ViewCompat.animate(mIndicator) + .alpha(1f) + .setDuration(200) + .withStartAction(new Runnable() { + @Override + public void run() { + mIndicator.setVisibility(View.VISIBLE); + } + }) + .start(); + } + + mIndicator.setText(String.valueOf(surplusLen)); + //noinspection deprecation + mIndicator.setTextColor(surplusLen >= 0 ? + getResources().getColor(R.color.tweet_indicator_text_color) : + getResources().getColor(R.color.tweet_indicator_text_color_error)); + } + } + }); + + // 设置键盘输入#或者@适合的监听器 + mEditContent.setOnKeyArrivedListener(new OnKeyArrivedListenerAdapterV2(this)); + mEditContent.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + mFacePanel.hidePanel(); + return false; + } + }); + + // Show keyboard + showSoftKeyboard(mEditContent); + } + + private void setSendIconStatus(boolean haveContent, String content) { + if (haveContent) { + content = content.trim(); + haveContent = !TextUtils.isEmpty(content); + } + mIconSend.setEnabled(haveContent); + } + + + @Override + protected void initData() { + super.initData(); + mOperator.loadData(); + } + + // 用于拦截后续的点击事件 + private long mLastClickTime; + + @OnClick({R.id.iv_picture, R.id.iv_mention, R.id.iv_tag, + R.id.iv_emoji, R.id.txt_indicator, R.id.icon_back, + R.id.icon_send, R.id.edit_content}) + @Override + public void onClick(View v) { + // 用来解决快速点击多个按钮弹出多个界面的情况 + long nowTime = System.currentTimeMillis(); + if ((nowTime - mLastClickTime) < 500) + return; + mLastClickTime = nowTime; + + try { + switch (v.getId()) { + case R.id.iv_picture: + hideAllKeyBoard(); + mLayImages.onLoadMoreClick(); + break; + case R.id.iv_mention: + hideAllKeyBoard(); + toSelectFriends(); + break; + case R.id.iv_tag: + toSelectTopic(); + break; + case R.id.iv_emoji: + handleEmojiClick(v); + break; + case R.id.txt_indicator: + handleClearContentClick(); + break; + case R.id.icon_back: + mOperator.onBack(); + break; + case R.id.icon_send: + mOperator.publish(); + break; + case R.id.edit_content: { + mFacePanel.hidePanel(); + } + break; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void handleClearContentClick() { + if (mIndicator.isSelected()) { + mIndicator.setSelected(false); + mEditContent.setText(""); + } else { + mIndicator.setSelected(true); + mIndicator.postDelayed(new Runnable() { + @Override + public void run() { + mIndicator.setSelected(false); + } + }, 1000); + } + } + + /** + * Emoji 表情点击 + * + * @param v View + */ + private void handleEmojiClick(View v) { + + if (mFacePanel.isShow()) { + mFacePanel.hidePanel(); + showSoftKeyboard(mEditContent); + } else { + TDevice.closeKeyboard(mEditContent); + mFacePanel.postDelayed(new Runnable() { + @Override + public void run() { + mFacePanel.openPanel(); + } + },250); + } + } + + /** + * 跳转选择话题 + */ + private void toSelectTopic() { + Context context = getContext(); + if (context == null) + return; + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(context); + return; + } + + TweetTopicActivity.show(this, mEditContent); + } + + + /** + * 跳转选择好友 + */ + private void toSelectFriends() { + Context context = getContext(); + if (context == null) + return; + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(context); + return; + } + + UserSelectFriendsActivity.show(this, mEditContent); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == Activity.RESULT_OK) { + switch (requestCode) { + case REQUEST_CODE_SELECT_FRIENDS: + // Nun Do handleSelectFriendsResult(data); + break; + case REQUEST_CODE_SELECT_TOPIC: + // Nun Do handleSelectTopicResult(data); + break; + } + } + + mEditContent.postDelayed(new Runnable() { + @Override + public void run() { + showSoftKeyboard(mEditContent); + } + }, 200); + } + + private void hideSoftKeyboard() { + mEditContent.clearFocus(); + ((InputMethodManager) getActivity().getSystemService( + Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow( + mEditContent.getWindowToken(), 0); + } + + private void showSoftKeyboard(final EditText requestView) { + if (requestView == null) + return; + requestView.requestFocus(); + ((InputMethodManager) getActivity().getSystemService( + Context.INPUT_METHOD_SERVICE)).showSoftInput(requestView, + InputMethodManager.SHOW_FORCED); + } + + private void hideAllKeyBoard() { + mFacePanel.hidePanel(); + hideSoftKeyboard(); + } + + @Override + public String getContent() { + return mEditContent.getText().toString(); + } + + @Override + public void setContent(String content, boolean needSelectionEnd) { + Spannable span = InputHelper.displayEmoji(getResources(), content, (int) mEditContent.getTextSize()); + mEditContent.setText(span); + //if (needSelectionEnd) + mEditContent.setSelection(mEditContent.getText().length()); + } + + @Override + public void setAbout(About.Share share, boolean needCommit) { + if (TextUtils.isEmpty(share.title) && TextUtils.isEmpty(share.content)) + return; + // Change the layout visibility + mLayImages.setVisibility(View.GONE); + setVisibility(R.id.lay_about); + // Set title and content + ((TextView) findView(R.id.txt_about_title)).setText(share.type == OSChinaApi.COMMENT_TWEET ? + "@" + share.title : share.title); + ((TextView) findView(R.id.txt_about_content)).setText(TweetParser.getInstance().clearHtmlTag(share.content)); + findView(R.id.iv_picture).setEnabled(false); + + if (needCommit) + setVisibility(R.id.cb_commit_control); + else + setGone(R.id.cb_commit_control); + } + + @Override + public boolean needCommit() { + return ((CheckBox) findView(R.id.cb_commit_control)).isChecked(); + } + + @Override + public String[] getImages() { + return mLayImages.getPaths(); + } + + @Override + public void setImages(String[] paths) { + mLayImages.set(paths); + } + + @Override + public void finish() { + // hide key board before finish + hideAllKeyBoard(); + // finish + Activity activity = getActivity(); + if (activity != null && activity instanceof BaseBackActivity) { + ((BaseBackActivity) activity).onSupportNavigateUp(); + } + } + + @Override + public TweetPublishContract.Operator getOperator() { + return mOperator; + } + + @Override + public boolean onBackPressed() { + if (mFacePanel.isShow()) { + mFacePanel.hidePanel(); + return false; + } + return true; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mOperator.onSaveInstanceState(outState); + } + + @Override + protected void onRestartInstance(Bundle bundle) { + super.onRestartInstance(bundle); + if (bundle != null) + mOperator.onRestoreInstanceState(bundle); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/service/Contract.java b/app/src/main/java/net/oschina/app/improve/tweet/service/Contract.java new file mode 100644 index 0000000000000000000000000000000000000000..b74d5514b48e59dc97d5fcaa8bbc56ad1a461f04 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/service/Contract.java @@ -0,0 +1,26 @@ +package net.oschina.app.improve.tweet.service; + +/** + * Created by JuQiu + * on 16/7/21. + */ + +interface Contract { + interface IService { + String getCachePath(String id); + + void start(String modelId, IOperator operator); + + void stop(String id, int startId); + + void notifyMsg(int notifyId, String modelId, boolean haveReDo, boolean haveDelete, int resId, Object... values); + + void notifyCancel(int notifyId); + + void updateModelCache(String id, TweetPublishModel model); + } + + interface IOperator extends Runnable { + void stop(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/service/LopperResponseHandler.java b/app/src/main/java/net/oschina/app/improve/tweet/service/LopperResponseHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..2f51d99bdfa5c9bb68b1ff0e954da092e266da6c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/service/LopperResponseHandler.java @@ -0,0 +1,85 @@ +package net.oschina.app.improve.tweet.service; + +import android.os.Looper; + +import com.loopj.android.http.AsyncHttpClient; +import com.loopj.android.http.AsyncHttpResponseHandler; + +import java.io.UnsupportedEncodingException; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by JuQiu + * on 16/7/19. + */ +@SuppressWarnings("WeakerAccess") +public abstract class LopperResponseHandler extends AsyncHttpResponseHandler { + private static final String LOG_TAG = "TextHttpRH"; + + /** + * Creates new instance with default UTF-8 encoding + */ + public LopperResponseHandler() { + this(DEFAULT_CHARSET); + } + + /** + * Creates new instance with given string encoding + * + * @param encoding String encoding, see {@link #setCharset(String)} + */ + public LopperResponseHandler(String encoding) { + super(Looper.myLooper()); + setCharset(encoding); + } + + /** + * Attempts to encode response bytes as string of set encoding + * + * @param charset charset to create string with + * @param stringBytes response bytes + * @return String of set encoding or null + */ + public static String getResponseString(byte[] stringBytes, String charset) { + try { + String toReturn = (stringBytes == null) ? null : new String(stringBytes, charset); + if (toReturn != null && toReturn.startsWith(UTF8_BOM)) { + return toReturn.substring(1); + } + return toReturn; + } catch (UnsupportedEncodingException e) { + AsyncHttpClient.log.e(LOG_TAG, "Encoding response into string failed", e); + return null; + } + } + + /** + * Called when request fails + * + * @param statusCode http response status line + * @param headers response headers if any + * @param responseString string response of given charset + * @param throwable throwable returned when processing request + */ + public abstract void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable); + + /** + * Called when request succeeds + * + * @param statusCode http response status line + * @param headers response headers if any + * @param responseString string response of given charset + */ + public abstract void onSuccess(int statusCode, Header[] headers, String responseString); + + @Override + public void onSuccess(int statusCode, Header[] headers, byte[] responseBytes) { + onSuccess(statusCode, headers, getResponseString(responseBytes, getCharset())); + } + + @Override + public void onFailure(int statusCode, Header[] headers, byte[] responseBytes, Throwable throwable) { + onFailure(statusCode, headers, getResponseString(responseBytes, getCharset()), throwable); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/service/TweetNotificationManager.java b/app/src/main/java/net/oschina/app/improve/tweet/service/TweetNotificationManager.java new file mode 100644 index 0000000000000000000000000000000000000000..2ab0f87e6fcd3a0da0b3ce87f8ce81a1a415e878 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/service/TweetNotificationManager.java @@ -0,0 +1,196 @@ +package net.oschina.app.improve.tweet.service; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import net.oschina.app.AppContext; +import net.oschina.app.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by jzz + * on 2017/2/20. + * desc: + */ +public class TweetNotificationManager { + private static TweetNotificationManager INSTANCE; + private PubTweetReceiver mPubTweetReceiver; + private List mNotifies = new ArrayList<>(); + + private TweetNotificationManager() { + } + + private static TweetNotificationManager instance() { + if (INSTANCE == null) { + synchronized (TweetNotificationManager.class) { + if (INSTANCE == null) { + INSTANCE = new TweetNotificationManager(); + } + } + } + return INSTANCE; + } + + public static synchronized void setup(Context context) { + if (context == null) + return; + Context appContext = context.getApplicationContext(); + if (appContext == null) + return; + TweetNotificationManager manager = instance(); + if (manager.mPubTweetReceiver == null) { + // 注册广播 + PubTweetReceiver pubTweetReceiver = new PubTweetReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(TweetPublishService.ACTION_SUCCESS); + filter.addAction(TweetPublishService.ACTION_FAILED); + filter.addAction(TweetPublishService.ACTION_PROGRESS); + filter.addAction(TweetPublishService.ACTION_PUBLISH); + filter.addAction(TweetPublishService.ACTION_CONTINUE); + filter.addAction(TweetPublishService.ACTION_DELETE); + filter.addAction(TweetPublishService.ACTION_RECEIVER_SEARCH_FAILED); + appContext.registerReceiver(pubTweetReceiver, filter); + manager.mPubTweetReceiver = pubTweetReceiver; + // 添加全局通知Toast + manager.mNotifies.add(new ToastNotify()); + } + } + + public static synchronized void destroy(Context context) { + if (context == null) + return; + Context appContext = context.getApplicationContext(); + if (appContext == null) + return; + PubTweetReceiver pubTweetReceiver = instance().mPubTweetReceiver; + if (pubTweetReceiver != null) { + appContext.unregisterReceiver(pubTweetReceiver); + instance().mPubTweetReceiver = null; + } + } + + public static void bindNotify(Context context, TweetPubNotify tweetPubNotify) { + // 绑定前进行初始化操作 + setup(context); + + //检查是否已经存在 + TweetNotificationManager manager = instance(); + boolean contains = manager.mNotifies.contains(tweetPubNotify); + if (!contains) + manager.mNotifies.add(tweetPubNotify); + } + + public static void unBoundNotify(TweetPubNotify tweetPubNotify) { + instance().mNotifies.remove(tweetPubNotify); + } + + private static class PubTweetReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (intent != null) { + List notifies = instance().mNotifies; + switch (intent.getAction()) { + case TweetPublishService.ACTION_SUCCESS: + //动弹发送成功 + for (TweetPubNotify notify : notifies) { + notify.onTweetPubSuccess(); + } + break; + case TweetPublishService.ACTION_FAILED: + //发送动弹失败 + for (TweetPubNotify notify : notifies) { + notify.onTweetPubFailed(); + } + break; + case TweetPublishService.ACTION_PROGRESS: + //更新动弹发送进度 + + String progressContent = intent.getStringExtra(TweetPublishService.EXTRA_PROGRESS); + + for (TweetPubNotify notify : notifies) { + notify.onTweetPubProgress(progressContent); + } + break; + case TweetPublishService.ACTION_PUBLISH: + //开始发送动弹,监听发布进度起点 + break; + case TweetPublishService.ACTION_CONTINUE: + //重新尝试发送该条动弹 + for (TweetPubNotify notify : notifies) { + notify.onTweetPubContinue(); + } + break; + case TweetPublishService.ACTION_DELETE: + //忽略该条动弹之后,直接gone草稿箱view + for (TweetPubNotify notify : notifies) { + notify.onTweetPubDelete(); + } + break; + case TweetPublishService.ACTION_RECEIVER_SEARCH_FAILED: + //动态发送失败缓存查询 + String[] pubFailedCacheIds = intent.getStringArrayExtra(TweetPublishService.EXTRA_IDS); + + for (TweetPubNotify notify : notifies) { + notify.pnTweetReceiverSearchFailed(pubFailedCacheIds); + } + break; + default: + break; + } + } + } + } + + private static class ToastNotify implements TweetPubNotify { + + @Override + public void onTweetPubSuccess() { + AppContext.showToastShort(R.string.tweet_publish_success); + } + + @Override + public void onTweetPubFailed() { + AppContext.showToastShort(R.string.tweet_publish_failed_hint); + } + + @Override + public void onTweetPubProgress(String progressContent) { + AppContext.showToast(progressContent); + } + + @Override + public void onTweetPubContinue() { + AppContext.showToastShort(R.string.tweet_retry_publishing_hint); + } + + @Override + public void onTweetPubDelete() { + + } + + @Override + public void pnTweetReceiverSearchFailed(String[] pubFailedCacheIds) { + + } + } + + public interface TweetPubNotify { + + void onTweetPubSuccess(); + + void onTweetPubFailed(); + + void onTweetPubProgress(String progressContent); + + void onTweetPubContinue(); + + void onTweetPubDelete(); + + void pnTweetReceiverSearchFailed(String[] pubFailedCacheIds); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/service/TweetPublishCache.java b/app/src/main/java/net/oschina/app/improve/tweet/service/TweetPublishCache.java new file mode 100644 index 0000000000000000000000000000000000000000..a44eff94a3cf1e766cf5912ff85f54e2ea1c0d83 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/service/TweetPublishCache.java @@ -0,0 +1,164 @@ +package net.oschina.app.improve.tweet.service; + +import android.content.Context; + +import net.oschina.app.util.TLog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.List; + +import static net.oschina.common.utils.StreamUtil.close; + + +/** + * Created by JuQiu + * on 16/7/21. + */ +@SuppressWarnings("WeakerAccess") +public class TweetPublishCache { + private final static String TAG = TweetPublishCache.class.getName(); + + private TweetPublishCache() { + + } + + static String getImageCachePath(Context context, String id) { + return String.format("%s/TweetPictures/%s", context.getCacheDir().getAbsolutePath(), id); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + static String getFileCachePath(Context context, String id) { + String dir = context.getFilesDir().getAbsolutePath() + "/TweetQueue"; + File file = new File(dir); + if (!file.exists()) { + file.mkdirs(); + } + if (id != null) { + return String.format("%s/%s.tweet", dir, id); + } + return dir; + } + + static String getIdByFile(File file) { + if (file == null) + return null; + String name = file.getName(); + int index = name.indexOf(".tweet"); + if (index == -1) + return name; + return name.substring(0, index); + } + + static void removeImages(Context context, String id) { + String dir = getImageCachePath(context, id); + File file = new File(dir); + if (file.exists() && file.isDirectory()) { + deleteDir(file); + } + } + + public static boolean have(Context context, String id) { + File data = new File(getFileCachePath(context, id)); + return data.exists(); + } + + public static List list(Context context) { + File fileDir = new File(getFileCachePath(context, null)); + if (fileDir.exists() && fileDir.isDirectory()) { + File[] files = fileDir.listFiles(); + if (files != null && fileDir.length() > 0) { + List models = new ArrayList<>(); + for (File file : files) { + String id = getIdByFile(file); + TweetPublishModel model = get(context, id); + if (model != null) + models.add(model); + } + return models; + } + } + return null; + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + public static boolean save(Context context, String id, TweetPublishModel model) { + final String path = getFileCachePath(context, id); + log("save", path); + FileOutputStream fos = null; + ObjectOutputStream oos = null; + try { + File file = new File(path); + if (file.exists()) + file.delete(); + file.createNewFile(); + fos = new FileOutputStream(path); + oos = new ObjectOutputStream(fos); + oos.writeObject(model); + oos.flush(); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } finally { + close(oos, fos); + } + } + + public static TweetPublishModel get(Context context, String id) { + if (!have(context, id)) + return null; + + final String path = getFileCachePath(context, id); + log("get", path); + FileInputStream fis = null; + ObjectInputStream ois = null; + try { + fis = new FileInputStream(path); + ois = new ObjectInputStream(fis); + return (TweetPublishModel) ois.readObject(); + } catch (FileNotFoundException ignored) { + } catch (InvalidClassException e) { + e.printStackTrace(); + remove(context, id); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } finally { + close(ois, fis); + } + return null; + } + + public static boolean remove(Context context, String id) { + // To clear the images cache + removeImages(context, id); + + File data = new File(getFileCachePath(context, id)); + log("remove", data.getAbsolutePath()); + return !data.exists() || data.delete(); + } + + + @SuppressWarnings("ResultOfMethodCallIgnored") + private static void deleteDir(File dir) { + if (dir.isDirectory()) { + String[] children = dir.list(); + for (String c : children) { + deleteDir(new File(dir, c)); + } + } + log("delete", dir.getAbsolutePath()); + dir.delete(); + } + + private static void log(String action, String msg) { + TLog.e(TAG, String.format("%s:%s", action, msg)); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/service/TweetPublishModel.java b/app/src/main/java/net/oschina/app/improve/tweet/service/TweetPublishModel.java new file mode 100644 index 0000000000000000000000000000000000000000..4083c23977f2f46ea91b88c588ccecb5aaa9fb11 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/service/TweetPublishModel.java @@ -0,0 +1,104 @@ +package net.oschina.app.improve.tweet.service; + +import net.oschina.app.improve.bean.simple.About; + +import java.io.Serializable; +import java.util.UUID; + +/** + * Created by JuQiu + * on 16/7/21. + */ + +@SuppressWarnings("WeakerAccess") +public class TweetPublishModel implements Serializable { + private String id; + private long date; + private String content; + private String[] srcImages; + private String[] cacheImages; + private String cacheImagesToken; + private int cacheImagesIndex; + private String errorString; + private long aboutId; + private int aboutType; + private long aboutCommitId; + private long aboutFromTweetId; + + public TweetPublishModel() { + id = UUID.randomUUID().toString(); + date = System.currentTimeMillis(); + } + + public TweetPublishModel(String content, String[] images, About.Share share) { + this(); + this.content = content; + this.srcImages = images; + if (About.check(share)) { + this.aboutId = share.id; + this.aboutType = share.type; + this.aboutCommitId = share.commitTweetId; + this.aboutFromTweetId = share.fromTweetId; + } + } + + public String getId() { + return id; + } + + public long getDate() { + return date; + } + + public void setDate(long date) { + this.date = date; + } + + public String getContent() { + return content; + } + + public About.Share getAboutShare() { + About.Share share = About.buildShare(aboutId, aboutType); + if (About.check(share)) { + share.commitTweetId = aboutCommitId; + share.fromTweetId = aboutFromTweetId; + return share; + } + return null; + } + + public String[] getSrcImages() { + return srcImages; + } + + public String[] getCacheImages() { + return cacheImages; + } + + public String getCacheImagesToken() { + return cacheImagesToken; + } + + public int getCacheImagesIndex() { + return cacheImagesIndex; + } + + public void setCacheImages(String[] cacheImages) { + this.cacheImages = cacheImages; + this.srcImages = null; + } + + public void setCacheImagesInfo(int cacheImagesIndex, String cacheImagesToken) { + this.cacheImagesToken = cacheImagesToken; + this.cacheImagesIndex = cacheImagesIndex; + } + + public String getErrorString() { + return errorString; + } + + public void setErrorString(String errorString) { + this.errorString = errorString; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/service/TweetPublishOperator.java b/app/src/main/java/net/oschina/app/improve/tweet/service/TweetPublishOperator.java new file mode 100644 index 0000000000000000000000000000000000000000..bb2146628c5fab3fd756a06d2c3218eae02ebce4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/service/TweetPublishOperator.java @@ -0,0 +1,515 @@ +package net.oschina.app.improve.tweet.service; + +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.Log; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; +import com.upyun.library.common.UploadEngine; +import com.upyun.library.listener.UpCompleteListener; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.resource.ImageResource; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.utils.MD5; +import net.oschina.app.improve.utils.PicturesCompressor; +import net.oschina.common.utils.BitmapUtil; + +import java.io.File; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by JuQiu + * on 16/7/21. + * 动弹发布执行者 + */ +@SuppressWarnings("unused") +class TweetPublishOperator implements Runnable, Contract.IOperator { + private final int serviceStartId; + private final int notificationId; + private Contract.IService service; + private TweetPublishModel model; + + interface UploadImageCallback { + void onUploadImageDone(); + + void onUploadImage(int index, String token); + } + + TweetPublishOperator(TweetPublishModel model, Contract.IService service, int startId) { + this.model = model; + this.notificationId = model.getId().hashCode(); + this.serviceStartId = startId; + this.service = service; + } + + /** + * 执行动弹发布操作 + */ + @Override + public void run() { + // call to service + this.service.start(model.getId(), this); + // notify + notifyMsg(R.string.tweet_publishing); + // doing + final TweetPublishModel model = this.model; + if (model.getSrcImages() == null && model.getCacheImages() == null) { + // 当没有图片的时候,直接进行发布动弹 + publish(); + } else { + if (model.getCacheImages() == null) { + notifyMsg(R.string.tweet_image_wait); + final String cacheDir = service.getCachePath(model.getId()); + // change the model + model.setCacheImages(saveImageToCache(cacheDir, model.getSrcImages())); + // update to cache file + service.updateModelCache(model.getId(), model); + + if (model.getCacheImages() == null) { + notifyMsg(R.string.tweet_image_wait_failed); + //图片转存失败,注册失败广播 + AppContext.getInstance().sendBroadcast(new Intent(TweetPublishService.ACTION_FAILED)); + publish(); + return; + } + } + // 开始上传图片,并回调进度 + uploadImages(model.getCacheImagesIndex(), model.getCacheImagesToken(), model.getCacheImages(), + new UploadImageCallback() { + @Override + public void onUploadImageDone() { + publish(); + } + + @Override + public void onUploadImage(int index, String token) { + model.setCacheImagesInfo(index, token); + // update to cache file + service.updateModelCache(model.getId(), model); + } + }); +// getTokenAndUpload(model.getCacheImagesIndex(), model.getCacheImages(), new +// UploadImageCallback() { +// @Override +// public void onUploadImageDone() { +// publish(); +// } +// +// @Override +// public void onUploadImage(int index, String token) { +// model.setCacheImagesInfo(index, token); +// service.updateModelCache(model.getId(), model); +// } +// }); + + } + } + + + /** + * 步骤1,获取又拍凭证并上传文件 + * + * @param index index + * @param paths paths + * @param callback callback + */ + private void getTokenAndUpload(final int index, final String[] paths, final UploadImageCallback callback) { + OSChinaApi.getYPToken(new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + String error = ""; + String response = "上传失败"; + TweetPublishService.log(String.format("Upload image onFailure, statusCode:[%s] responseString:%s throwable:%s", + "error", response, error)); + setError(R.string.tweet_image_publish_failed, String.valueOf(index + 1), String.valueOf(paths.length)); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean resultBean = new Gson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + upload(index, resultBean.getResult(), paths, callback); + } else { + saveError("Upload", resultBean.getMessage()); + onFailure(statusCode, headers, responseString, null); + } + } catch (Exception e) { + saveError("Upload", "response parse error「" + responseString + "」"); + onFailure(statusCode, headers, responseString, null); + } + } + }); + } + + + private List mImages = new ArrayList<>(); + + private void upload(final int index, final YoupaiToken token, final String[] paths, final UploadImageCallback runnable) { + // checkShare done + if (index < 0 || index >= paths.length) { + uploadTokenUrl(model.getCacheImagesToken(), 0, runnable); + return; + } + + final String path = paths[index]; + + File file = new File(path); + String saveKey = String.format("app_%s.%s", MD5.get32MD5Str(AccountHelper.getUserId() + file.getName() + System.currentTimeMillis()), PicturesCompressor.getFileDiff(file)); + + HashMap map = new HashMap<>(); + map.put("bucket", "oscnet"); + map.put("save-key", saveKey); + map.put("expiration", System.currentTimeMillis()); + + UploadEngine.getInstance() + .formUpload(file, map, token.getOperator(), token.getSecret(), + new UpCompleteListener() { + @Override + public void onComplete(boolean isSuccess, String result) { + Log.e("result", "result" + result + " -- " + isSuccess); + try { + YouPaiResult bean = new Gson().fromJson(result, YouPaiResult.class); + if (bean != null) { + if (bean.getCode() == 200) { + mImages.add(bean); + upload(index + 1, token, paths, runnable); + } else { + String error = ""; + String response = bean.getMessage(); + TweetPublishService.log(String.format("Upload image onFailure, statusCode:[%s] responseString:%s throwable:%s", + bean.getCode(), response, error)); + setError(R.string.tweet_image_publish_failed, String.valueOf(index + 1), String.valueOf(paths.length)); + } + } else { + String error = ""; + String response = "上传失败"; + TweetPublishService.log(String.format("Upload image onFailure, statusCode:[%s] responseString:%s throwable:%s", + "error", response, error)); + setError(R.string.tweet_image_publish_failed, String.valueOf(index + 1), String.valueOf(paths.length)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }, null); + } + + + /** + * 将图片资源告诉后台 + */ + private void uploadTokenUrl(String token, final int index, final UploadImageCallback runnable) { + runnable.onUploadImage(index, token); + if (index >= mImages.size()) { + runnable.onUploadImageDone(); + return; + } + OSChinaApi.uploadImageForYouPai(token, mImages.get(index), + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + String error = ""; + String response = responseString == null ? "" : responseString; + if (throwable != null) { + throwable.printStackTrace(); + error = throwable.getMessage(); + if (error.contains("UnknownHostException") + || error.contains("Read error: ssl") + || error.contains("Connection timed out")) { + saveError("Upload", "network error"); + } else { + saveError("Upload", response + " " + error); + } + } + TweetPublishService.log(String.format("Upload image onFailure, statusCode:[%s] responseString:%s throwable:%s", + statusCode, response, error)); + setError(R.string.tweet_image_publish_failed, String.valueOf(index + 1), String.valueOf(mImages.size())); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean resultBean = new Gson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + String token = resultBean.getResult().getToken(); + uploadTokenUrl(token, index + 1, runnable); + } else { + saveError("Upload", resultBean.getMessage()); + onFailure(statusCode, headers, responseString, null); + } + } catch (Exception e) { + saveError("Upload", "response parse error「" + responseString + "」"); + onFailure(statusCode, headers, responseString, null); + } + } + }); + } + + + /** + * 上传图片 + * + * @param index 上次图片的坐标 + * @param token 上传Token + * @param paths 上传的路径数组 + * @param runnable 完全上传完成时回调 + */ + private void uploadImages(final int index, final String token, final String[] paths, final UploadImageCallback runnable) { + // call progress + runnable.onUploadImage(index, token); + + // checkShare done + if (index < 0 || index >= paths.length) { + runnable.onUploadImageDone(); + return; + } + + final String path = paths[index]; + + OSChinaApi.uploadImage(token, path, new LopperResponseHandler() { + @Override + public void onStart() { + super.onStart(); + notifyMsg(R.string.tweet_image_publishing, String.valueOf(paths.length - index)); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + String error = ""; + String response = responseString == null ? "" : responseString; + if (throwable != null) { + throwable.printStackTrace(); + error = throwable.getMessage(); + if (error.contains("UnknownHostException") + || error.contains("Read error: ssl") + || error.contains("Connection timed out")) { + saveError("Upload", "network error"); + } else { + saveError("Upload", response + " " + error); + } + } + TweetPublishService.log(String.format("Upload image onFailure, statusCode:[%s] responseString:%s throwable:%s", + statusCode, response, error)); + setError(R.string.tweet_image_publish_failed, String.valueOf(index + 1), String.valueOf(paths.length)); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean resultBean = new Gson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + String token = resultBean.getResult().getToken(); + uploadImages(index + 1, token, paths, runnable); + } else { + File file = new File(path); + TweetPublishService.log(String.format("Upload name:[%s] size:[%s] error:%s", + file.getAbsolutePath(), file.length(), resultBean.getMessage())); + saveError("Upload", resultBean.getMessage()); + onFailure(statusCode, headers, responseString, null); + } + } catch (Exception e) { + saveError("Upload", "response parse error「" + responseString + "」"); + onFailure(statusCode, headers, responseString, null); + } + } + }); + } + + @Override + public void stop() { + final Contract.IService service = this.service; + if (service != null) { + this.service = null; + service.stop(model.getId(), serviceStartId); + } + } + + /** + * 发布动弹 + */ + private void publish() { + OSChinaApi.pubTweet(model.getContent(), model.getCacheImagesToken(), null, model.getAboutShare(), new LopperResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + String error = ""; + String response = responseString == null ? "" : responseString; + if (throwable != null) { + throwable.printStackTrace(); + error = throwable.getMessage(); + if (error.contains("UnknownHostException") + || error.contains("Read error: ssl") + || error.contains("Connection timed out")) { + saveError("Publish", "network error"); + } else { + saveError("Publish", response + " " + error); + } + } + + TweetPublishService.log(String.format("Publish tweet onFailure, statusCode:[%s] responseString:%s throwable:%s", + statusCode, response, error)); + setError(R.string.tweet_publish_failed); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken() { + }.getType(); + ResultBean resultBean = new Gson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + setSuccess(); + } else { + saveError("Publish", resultBean.getMessage()); + onFailure(statusCode, headers, responseString, null); + } + } catch (Exception e) { + saveError("Publish", "response parse error「" + responseString + "」"); + onFailure(statusCode, headers, responseString, null); + } + } + }); + } + + private void notifyMsg(int resId, Object... values) { + notifyMsg(false, resId, values); + } + + private void notifyMsg(boolean done, int resId, Object... values) { + Contract.IService service = this.service; + if (service != null) { + service.notifyMsg(notificationId, model.getId(), done, done, resId, values); + } + } + + private void setSuccess() { + AppContext.getInstance().sendBroadcast(new Intent(TweetPublishService.ACTION_SUCCESS)); + notifyMsg(R.string.tweet_publish_success); + try { + Thread.sleep(1600); + } catch (InterruptedException e) { + e.printStackTrace(); + } + Contract.IService service = this.service; + if (service != null) { + // clear the cache + service.updateModelCache(model.getId(), null); + // hide the notify + service.notifyCancel(notificationId); + } + + // Check the about commit id + if (!checkToCommit()) + stop(); + } + + private void setError(int resId, Object... values) { + AppContext.getInstance().sendBroadcast(new Intent(TweetPublishService.ACTION_FAILED)); + notifyMsg(true, resId, values); + stop(); + } + + private boolean checkToCommit() { + // 如果相关节点中定义了评论参数,那么将执行评论 + About.Share share = model.getAboutShare(); + if (About.check(share) && share.commitTweetId > 0) { + OSChinaApi.pubTweetComment(share.commitTweetId, model.getContent(), 0, new LopperResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + } + + @Override + public void onFinish() { + super.onFinish(); + stop(); + } + }); + return true; + } + return false; + } + + + // Max upload 860KB/3M + private static final long MAX_UPLOAD_LENGTH = 3072 * 1024; + + /** + * 保存文件到缓存中 + * + * @param cacheDir 缓存文件夹 + * @param paths 原始路径 + * @return 转存后的路径 + */ + private static String[] saveImageToCache(String cacheDir, String[] paths) { + List ret = new ArrayList<>(); + //byte[] buffer = new byte[BitmapUtil.DEFAULT_BUFFER_SIZE]; + BitmapFactory.Options options = BitmapUtil.createOptions(); + for (final String path : paths) { + String ext = null; + try { + int lastDotIndex = path.lastIndexOf("."); + if (lastDotIndex != -1) + ext = path.substring(lastDotIndex + 1).toLowerCase(); + } catch (Exception e) { + e.printStackTrace(); + } + + if (TextUtils.isEmpty(ext)) { + ext = "jpg"; + } + + try { + String tempFile = String.format("%s/IMG_%s.%s", cacheDir, SystemClock.currentThreadTimeMillis(), ext); + if (PicturesCompressor.compressImage(path, tempFile, MAX_UPLOAD_LENGTH, + 80, 1280, 1280 * 16, null, options, true)) { + TweetPublishService.log("OPERATOR doImage:" + tempFile + " " + new File(tempFile).length()); + // verify the picture ext. + tempFile = PicturesCompressor.verifyPictureExt(tempFile); + ret.add(tempFile); + continue; + } + } catch (Exception e) { + e.printStackTrace(); + } + TweetPublishService.log("OPERATOR compressImage error:" + path); + } + if (ret.size() > 0) { + String[] images = new String[ret.size()]; + ret.toArray(images); + return images; + } + return null; + } + + private void saveError(String cmd, String log) { + AppContext.getInstance().sendBroadcast(new Intent(TweetPublishService.ACTION_FAILED)); + model.setErrorString(String.format("%s | %s", cmd, log)); + // update to cache file save error log + service.updateModelCache(model.getId(), model); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/service/TweetPublishService.java b/app/src/main/java/net/oschina/app/improve/tweet/service/TweetPublishService.java new file mode 100644 index 0000000000000000000000000000000000000000..c71041d24a1ba550064a1fb84d3e6e560f743732 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/service/TweetPublishService.java @@ -0,0 +1,393 @@ +package net.oschina.app.improve.tweet.service; + +import android.app.Application; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationManagerCompat; +import android.support.v4.util.ArrayMap; + +import net.oschina.app.OSCApplication; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.utils.ListenAccountChangeReceiver; +import net.oschina.app.util.TLog; +import net.oschina.common.utils.CollectionUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 动弹发布服务 + * 专用于动弹发布 + */ +public class TweetPublishService extends Service implements Contract.IService { + private final static String TAG = TweetPublishService.class.getName(); + + public static final String ACTION_RECEIVER_SEARCH_INCOMPLETE = "net.oschina.app.improve.tweet.service.action.receiver.SEARCH_INCOMPLETE"; + public static final String ACTION_RECEIVER_SEARCH_FAILED = "net.oschina.app.improve.tweet.service.action.receiver.SEARCH_FAILED"; + + public static final String ACTION_PUBLISH = "net.oschina.app.improve.tweet.service.action.PUBLISH"; + public static final String ACTION_CONTINUE = "net.oschina.app.improve.tweet.service.action.CONTINUE"; + public static final String ACTION_DELETE = "net.oschina.app.improve.tweet.service.action.DELETE"; + public static final String ACTION_FAILED = "net.oschina.app.improve.tweet.service.action.FAILED"; + public static final String ACTION_SUCCESS = "net.oschina.app.improve.tweet.service.action.SUCCESS"; + public static final String ACTION_PROGRESS = "net.oschina.app.improve.tweet.service.action.PROGRESS"; + + private static final String EXTRA_CONTENT = "net.oschina.app.improve.tweet.service.extra.CONTENT"; + public static final String EXTRA_PROGRESS = "net.oschina.app.improve.tweet.service.extra.CONTENT"; + private static final String EXTRA_IMAGES = "net.oschina.app.improve.tweet.service.extra.IMAGES"; + private static final String EXTRA_ABOUT = "net.oschina.app.improve.tweet.service.extra.ABOUT"; + private static final String EXTRA_ID = "net.oschina.app.improve.tweet.service.extra.ID"; + public static final String EXTRA_IDS = "net.oschina.app.improve.tweet.service.extra.IDS"; + + private volatile Looper mServiceLooper; + private volatile ServiceHandler mServiceHandler; + private Map mRunTasks = new ArrayMap<>(); + + private int mTaskCount; + private volatile boolean mDoAddTask = false; + private ListenAccountChangeReceiver mReceiver; + + private final class ServiceHandler extends Handler { + ServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + onHandleIntent((Intent) msg.obj, msg.arg1); + } + } + + + /** + * 发起动弹发布服务 + *

    + * 如果发布的动弹绑定到相关资讯等,则About节点不为NULL + * 仅仅关注:{@link About.Share#id}, {@link About.Share#type}}, + * {@link About.Share#commitTweetId}, {@link About.Share#fromTweetId} + */ + public static void startActionPublish(Context context, String content, List images, About.Share aboutShare) { + Intent intent = new Intent(context, TweetPublishService.class); + intent.setAction(ACTION_PUBLISH); + intent.putExtra(EXTRA_CONTENT, content); + if (images != null && images.size() > 0) { + String[] pubImages = new String[images.size()]; + images.toArray(pubImages); + intent.putExtra(EXTRA_IMAGES, pubImages); + } + if (About.check(aboutShare)) { + intent.putExtra(EXTRA_ABOUT, aboutShare); + } + context.startService(intent); + } + + /** + * 继续发送动弹 + * + * @param context Context + * @param modelId {@link TweetPublishModel#id} + */ + public static void startActionContinue(Context context, String modelId) { + Intent intent = new Intent(context, TweetPublishService.class); + intent.setAction(ACTION_CONTINUE); + intent.putExtra(EXTRA_ID, modelId); + context.startService(intent); + } + + /** + * 删除该动弹 + * + * @param context Context + * @param modelId {@link TweetPublishModel#id} + */ + public static void startActionDelete(Context context, String modelId) { + Intent intent = new Intent(context, TweetPublishService.class); + intent.setAction(ACTION_DELETE); + intent.putExtra(EXTRA_ID, modelId); + context.startService(intent); + } + + + /** + * 查询发送失败动弹 + * 如果有将通过广播{@link #ACTION_RECEIVER_SEARCH_FAILED}通知 + */ + public static void startActionSearchFailed(Context context) { + Intent intent = new Intent(context, TweetPublishService.class); + intent.setAction(ACTION_RECEIVER_SEARCH_FAILED); + context.startService(intent); + } + + public TweetPublishService() { + } + + private void addTask(int startId) { + mDoAddTask = true; + synchronized (TweetPublishService.this) { + mTaskCount = mTaskCount + 1; + log("removeTask:" + startId + " count:" + mTaskCount); + } + mDoAddTask = false; + } + + private void removeTask(int startId) { + synchronized (TweetPublishService.this) { + mTaskCount = mTaskCount - 1; + log("removeTask:" + startId + " count:" + mTaskCount + " doAdd:" + mDoAddTask); + if (mTaskCount == 0 && !mDoAddTask) + stopSelf(); + } + } + + @Override + public void onCreate() { + super.onCreate(); + log("onCreate"); + + // First init the Client + Application application = getApplication(); + if (application instanceof OSCApplication) { + OSCApplication.reInit(); + } + + HandlerThread thread = new HandlerThread(TweetPublishService.class.getSimpleName()); + thread.start(); + + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper); + mReceiver = ListenAccountChangeReceiver.start(this); + } + + /** + * You should not override this method for your IntentService. Instead, + * override {@link #onHandleIntent}, which the system calls when the IntentService + * receives a start request. + * + * @see android.app.Service#onStartCommand + */ + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // In this, we add the task count + addTask(startId); + // Do message + Message msg = mServiceHandler.obtainMessage(); + msg.arg1 = startId; + msg.obj = intent; + mServiceHandler.sendMessage(msg); + return super.onStartCommand(intent, flags, startId); + } + + @Override + public void onDestroy() { + mServiceLooper.quit(); + mReceiver.destroy(); + log("onDestroy"); + super.onDestroy(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + /** + * 在线程中处理请求数据 + * + * @param intent 请求的数据 + * @param startId 启动服务的Id + */ + private void onHandleIntent(Intent intent, int startId) { + if (intent != null) { + final String action = intent.getAction(); + log("onHandleIntent:" + startId); + log(action); + + if (ACTION_PUBLISH.equals(action)) { + final String content = intent.getStringExtra(EXTRA_CONTENT); + final String[] images = intent.getStringArrayExtra(EXTRA_IMAGES); + final About.Share share = (About.Share) intent.getSerializableExtra(EXTRA_ABOUT); + handleActionPublish(content, images, share, startId); + } else { + if (ACTION_CONTINUE.equals(action)) { + final String id = intent.getStringExtra(EXTRA_ID); + if (id == null || handleActionContinue(id, startId)) { + removeTask(startId); + } + } else if (ACTION_DELETE.equals(action)) { + final String id = intent.getStringExtra(EXTRA_ID); + if (id == null || handleActionDelete(id, startId)) { + removeTask(startId); + } + } else if (ACTION_RECEIVER_SEARCH_FAILED.equals(action)) { + handleActionSearch(); + removeTask(startId); + } + } + } + } + + /** + * 发布动弹,在后台服务中进行 + */ + private void handleActionPublish(String content, String[] images, About.Share share, int startId) { + TweetPublishModel model = new TweetPublishModel(content, images, share); + TweetPublishCache.save(getApplicationContext(), model.getId(), model); + Contract.IOperator operator = new TweetPublishOperator(model, this, startId); + operator.run(); + } + + /** + * 继续发送动弹 + * + * @param id 动弹Id + * @param startId 服务Id + * @return 返回是否销毁当前服务 + */ + private boolean handleActionContinue(String id, int startId) { + Contract.IOperator operator = mRunTasks.get(id); + if (operator != null) { + // 正在运行, 不做操作 + return true; + } + TweetPublishModel model = TweetPublishCache.get(getApplicationContext(), id); + if (model != null) { + operator = new TweetPublishOperator(model, this, startId); + operator.run(); + return false; + } + return true; + } + + /** + * 移除动弹 + * 该动弹的缓存将进行清空 + * + * @param id 动弹Id + * @param startId 服务Id + * @return 返回是否销毁当前服务 + */ + private boolean handleActionDelete(String id, int startId) { + Contract.IOperator operator = mRunTasks.get(id); + if (operator != null) + operator.stop(); + TweetPublishCache.remove(getApplicationContext(), id); + // In this we need remove the notify + notifyCancel(id.hashCode()); + return true; + } + + /** + * 进行查询操作, + * 查询当前发送失败的动弹 + */ + private boolean handleActionSearch() { + List models = TweetPublishCache.list(getApplicationContext()); + if (models == null || models.size() == 0) + return false; + + final Map runningMap = mRunTasks; + List ids = new ArrayList<>(); + + for (TweetPublishModel model : models) { + if (!runningMap.containsKey(model.getId())) { + ids.add(model.getId()); + } + } + + // If have failed task, we need callback + if (ids.size() > 0) { + Intent intent = new Intent(ACTION_RECEIVER_SEARCH_FAILED); + intent.putExtra(EXTRA_IDS, CollectionUtil.toArray(ids, String.class)); + sendBroadcast(intent); + return true; + } + + return false; + } + + @Override + public String getCachePath(String id) { + return TweetPublishCache.getImageCachePath(getApplicationContext(), id); + } + + @Override + public void start(String id, Contract.IOperator operator) { + if (!mRunTasks.containsKey(id)) { + mRunTasks.put(id, operator); + } + } + + @Override + public void stop(String id, int startId) { + if (mRunTasks.containsKey(id)) { + mRunTasks.remove(id); + } + // stop self + removeTask(startId); + } + + @Override + public void updateModelCache(String id, TweetPublishModel model) { + if (model == null) + TweetPublishCache.remove(getApplicationContext(), id); + else + TweetPublishCache.save(getApplicationContext(), id, model); + } + + @Override + public void notifyMsg(int notifyId, String modelId, boolean haveReDo, boolean haveDelete, int resId, Object... values) { +// PendingIntent contentIntent = null; +// if (haveReDo) { +// Intent intent = new Intent(this, TweetPublishService.class); +// intent.setAction(ACTION_CONTINUE); +// intent.putExtra(EXTRA_ID, modelId); +// contentIntent = PendingIntent.getService(getApplicationContext(), notifyId, intent, PendingIntent.FLAG_UPDATE_CURRENT); +// } +// +// PendingIntent deleteIntent = null; +// if (haveDelete) { +// Intent intent = new Intent(this, TweetPublishService.class); +// intent.setAction(ACTION_DELETE); +// intent.putExtra(EXTRA_ID, modelId); +// deleteIntent = PendingIntent.getService(getApplicationContext(), notifyId, intent, PendingIntent.FLAG_UPDATE_CURRENT); +// } +// + String content = getString(resId, values); +// NotificationCompat.Builder builder = new NotificationCompat.Builder(this) +// .setTicker(content) +// .setContentTitle(getString(R.string.tweet_publish_title)) +// .setContentText(content) +// .setAutoCancel(haveDelete) +// .setOngoing(!haveReDo) +// .setContentIntent(contentIntent) +// .setDeleteIntent(deleteIntent) +// .setSmallIcon(R.mipmap.ic_notification); + + //Notification notification = builder.build(); + //NotificationManagerCompat.from(this).notify(notifyId, notification); + + Intent intent = new Intent(ACTION_PROGRESS); + intent.putExtra(EXTRA_PROGRESS, content); + sendBroadcast(intent); + + log(content); + } + + @Override + public void notifyCancel(int notifyId) { + NotificationManagerCompat.from(this).cancel(notifyId); + } + + public static void log(String str) { + TLog.log(TAG, str); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/service/YouPaiResult.java b/app/src/main/java/net/oschina/app/improve/tweet/service/YouPaiResult.java new file mode 100644 index 0000000000000000000000000000000000000000..085ca07b80377ac2cddc659d24123c19a23eb642 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/service/YouPaiResult.java @@ -0,0 +1,81 @@ +package net.oschina.app.improve.tweet.service; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +public final class YouPaiResult implements Serializable { + @SerializedName("image-type") + private String imageType; + + @SerializedName("image-frames") + private int imageFrames; + + @SerializedName("image-height") + private int imageHeight; + + @SerializedName("image-width") + private int imageWidth; + + private int code; + + private String url; + + private String message; + + public String getImageType() { + return imageType; + } + + public void setImageType(String imageType) { + this.imageType = imageType; + } + + public int getImageFrames() { + return imageFrames; + } + + public void setImageFrames(int imageFrames) { + this.imageFrames = imageFrames; + } + + public int getImageHeight() { + return imageHeight; + } + + public void setImageHeight(int imageHeight) { + this.imageHeight = imageHeight; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getImageWidth() { + return imageWidth; + } + + public void setImageWidth(int imageWidth) { + this.imageWidth = imageWidth; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/service/YoupaiToken.java b/app/src/main/java/net/oschina/app/improve/tweet/service/YoupaiToken.java new file mode 100644 index 0000000000000000000000000000000000000000..65ec6dc8664a5c6669fa5f0594efa69a3de9ee58 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/service/YoupaiToken.java @@ -0,0 +1,25 @@ +package net.oschina.app.improve.tweet.service; + + +import java.io.Serializable; + + class YoupaiToken implements Serializable{ + private String secret; + private String operator; + + public String getSecret() { + return secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + } diff --git a/app/src/main/java/net/oschina/app/improve/tweet/share/ShareCommentAdapter.java b/app/src/main/java/net/oschina/app/improve/tweet/share/ShareCommentAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..f223ac922dc299b74aca55ed994ef8269dd12c3e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/share/ShareCommentAdapter.java @@ -0,0 +1,171 @@ +package net.oschina.app.improve.tweet.share; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.emoji.InputHelper; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.TweetComment; +import net.oschina.app.improve.comment.CommentsUtil; +import net.oschina.app.improve.utils.parser.TweetParser; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.improve.widget.TweetPicturesLayout; +import net.oschina.app.util.StringUtils; +import net.oschina.app.widget.TweetTextView; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * 评论分享 + * Created by huanghaibin on 2017/10/18. + */ + +public class ShareCommentAdapter extends BaseRecyclerAdapter implements BaseRecyclerAdapter.OnLoadingHeaderCallBack { + + private Tweet mTweet; + + ShareCommentAdapter(Context context, int mode, Tweet tweet) { + super(context, mode); + this.mTweet = tweet; + setOnLoadingHeaderCallBack(this); + } + + @Override + public RecyclerView.ViewHolder onCreateHeaderHolder(ViewGroup parent) { + return new CommentHeaderView(LayoutInflater.from(mContext).inflate(R.layout.layout_tweet_share_header_view, parent, false)); + } + + @Override + public void onBindHeaderHolder(RecyclerView.ViewHolder holder, int position) { + Tweet tweet = mTweet; + Author author = tweet.getAuthor(); + CommentHeaderView h = (CommentHeaderView) holder; + if (author != null) { + h.ivPortrait.setup(author); + h.tvNick.setText(author.getName()); + } else { + h.ivPortrait.setup(0, "匿名用户", ""); + h.tvNick.setText("匿名用户"); + } + if (!TextUtils.isEmpty(tweet.getPubDate())) + h.tvTime.setText(StringUtils.getDateString(tweet.getPubDate())); + if (!TextUtils.isEmpty(tweet.getContent())) { + String content = tweet.getContent().replaceAll("[\n\\s]+", " "); + CommentsUtil.formatHtml(h.mContent.getResources(), h.mContent, content, true,false); + } + + h.mLayoutGrid.setImage(tweet.getImages()); + + /* -- about reference -- */ + if (tweet.getAbout() != null) { + h.mLayoutRef.setVisibility(View.VISIBLE); + About about = tweet.getAbout(); + h.mLayoutRefImages.setImage(about.getImages()); + + if (!About.check(about)) { + h.mViewRefTitle.setVisibility(View.VISIBLE); + h.mViewRefTitle.setText("不存在或已删除的内容"); + h.mViewRefContent.setText("抱歉,该内容不存在或已被删除"); + } else { + if (about.getType() == OSChinaApi.COMMENT_TWEET) { + h.mViewRefTitle.setVisibility(View.GONE); + String con = "@" + about.getTitle() + ": " +about.getContent(); + CommentsUtil.formatHtml(h.mViewRefContent.getResources(), h.mViewRefContent, con, true,true); + } else { + h.mViewRefTitle.setVisibility(View.VISIBLE); + h.mViewRefTitle.setText(about.getTitle()); + h.mViewRefContent.setText(about.getContent()); + } + } + } else { + h.mLayoutRef.setVisibility(View.GONE); + } + h.mTextComment.setVisibility(mItems.size() == 0 ? View.GONE : View.VISIBLE); + h.mTextCommentCount.setText(String.valueOf(tweet.getCommentCount())); + h.mTextLikeCount.setText(String.valueOf(tweet.getLikeCount())); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new CommentHolderView(LayoutInflater.from(mContext).inflate(R.layout.item_list_share_comment, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, TweetComment item, int position) { + CommentHolderView h = (CommentHolderView) holder; + + h.identityView.setup(item.getAuthor()); + h.ivPortrait.setup(item.getAuthor()); + h.ivPortrait.setTag(R.id.iv_tag, item); + h.tvName.setText(item.getAuthor().getName()); + h.tvContent.setText(InputHelper.displayEmoji(mContext.getResources(), item.getContent())); + h.tvTime.setText(StringUtils.formatSomeAgo(item.getPubDate())); + } + + static final class CommentHeaderView extends RecyclerView.ViewHolder { + @Bind(R.id.iv_portrait) + PortraitView ivPortrait; + @Bind(R.id.tv_nick) + TextView tvNick; + @Bind(R.id.tv_time) + TextView tvTime; + @Bind(R.id.tv_content) + TextView mContent; + @Bind(R.id.tweet_pics_layout) + TweetPicturesLayout mLayoutGrid; + @Bind(R.id.tv_ref_title) + TextView mViewRefTitle; + @Bind(R.id.tv_ref_content) + TextView mViewRefContent; + @Bind(R.id.layout_ref_images) + TweetPicturesLayout mLayoutRefImages; + @Bind(R.id.layout_ref) + LinearLayout mLayoutRef; + @Bind(R.id.tv_tweet_like_count) + TextView mTextLikeCount; + @Bind(R.id.tv_tweet_comment_count) + TextView mTextCommentCount; + @Bind(R.id.tv_comment) + TextView mTextComment; + + CommentHeaderView(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } + + public static final class CommentHolderView extends RecyclerView.ViewHolder { + @Bind(R.id.identityView) + IdentityView identityView; + @Bind(R.id.iv_avatar) + PortraitView ivPortrait; + @Bind(R.id.tv_name) + TextView tvName; + @Bind(R.id.tv_pub_date) + public TextView tvTime; + @Bind(R.id.tv_content) + TweetTextView tvContent; + + CommentHolderView(View view) { + super(view); + ButterKnife.bind(this, view); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/tweet/share/TweetShareActivity.java b/app/src/main/java/net/oschina/app/improve/tweet/share/TweetShareActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..eff2dea4e620ec94ff2ed64fd5d8e034119a1a5b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/share/TweetShareActivity.java @@ -0,0 +1,194 @@ +package net.oschina.app.improve.tweet.share; + +import android.Manifest; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.TweetComment; +import net.oschina.app.improve.utils.DialogHelper; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; +import cz.msebera.android.httpclient.Header; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * 动弹分享 + * Created by huanghaibin on 2017/10/16. + */ + +public class TweetShareActivity extends BackActivity implements + EasyPermissions.PermissionCallbacks, + View.OnClickListener { + + + private int mType; + private static final int TYPE_SHARE = 1; + private static final int TYPE_SAVE = 2; + private TweetShareFragment mFragment; + + @Bind(R.id.recyclerView) + RecyclerView mRecycleView; + private ShareCommentAdapter mAdapter; + private ArrayList mComments; + + public static void show(Context context, Tweet tweet) { + if (tweet == null) + return; + Intent intent = new Intent(context, TweetShareActivity.class); + intent.putExtra("tweet", tweet); + context.startActivity(intent); + } + + public static void show(Context context, Tweet tweet, List comments) { + if (tweet == null) + return; + Intent intent = new Intent(context, TweetShareActivity.class); + intent.putExtra("tweet", tweet); + if (comments != null && comments instanceof ArrayList) { + intent.putExtra("comments", (ArrayList) comments); + } + context.startActivity(intent); + } + + + @Override + protected int getContentView() { + return R.layout.activity_tweet_share; + } + + @SuppressWarnings("ALL") + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + Tweet tweet = (Tweet) getIntent().getSerializableExtra("tweet"); + mComments = (ArrayList) getIntent().getSerializableExtra("comments"); + + mRecycleView.setLayoutManager(new LinearLayoutManager(this)); + mAdapter = new ShareCommentAdapter(this, BaseRecyclerAdapter.ONLY_HEADER, tweet); + mRecycleView.setAdapter(mAdapter); + mAdapter.resetItem(mComments); + + mFragment = TweetShareFragment.newInstance(tweet, mComments); + addFragment(R.id.fl_content, mFragment); + requestData(tweet.getId()); + } + + @OnClick({R.id.ll_share, R.id.ll_save}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.ll_share: + mType = TYPE_SHARE; + saveToFileByPermission(); + break; + case R.id.ll_save: + mType = TYPE_SAVE; + saveToFileByPermission(); + break; + } + } + + + private void requestData(long id) { + OSChinaApi.getTweetCommentList(id, "", new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (isDestroy()) + return; + mAdapter.setState(BaseRecyclerAdapter.STATE_INVALID_NETWORK, true); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (isDestroy()) + return; + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean != null && resultBean.isSuccess() && resultBean.getResult().getItems() != null) { + if (mComments == null || mComments.size() == 0) { + mAdapter.resetItem(resultBean.getResult().getItems()); + } + mFragment.initList(resultBean.getResult().getItems()); + } else { + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + } + } catch (Exception e) { + e.printStackTrace(); + mAdapter.setState(BaseRecyclerAdapter.STATE_INVALID_NETWORK, true); + } + } + }); + } + + private static final int PERMISSION_ID = 0x0001; + + @SuppressWarnings("unused") + @AfterPermissionGranted(PERMISSION_ID) + public void saveToFileByPermission() { + String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + if (EasyPermissions.hasPermissions(this, permissions)) { + if (mType == TYPE_SHARE) { + mFragment.share(); + } else { + mFragment.save(); + } + } else { + EasyPermissions.requestPermissions(this, "请授予文件读写权限", PERMISSION_ID, permissions); + } + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + DialogHelper.getConfirmDialog(this, "", "没有权限, 你需要去设置中开启读取手机存储权限.", "去设置", "取消", false, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + //finish(); + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //finish(); + } + }).show(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/share/TweetShareFragment.java b/app/src/main/java/net/oschina/app/improve/tweet/share/TweetShareFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..2742312bd1a0d0b951d93d015d332a3737d78376 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/share/TweetShareFragment.java @@ -0,0 +1,271 @@ +package net.oschina.app.improve.tweet.share; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.support.v4.widget.NestedScrollView; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.TweetComment; +import net.oschina.app.improve.comment.CommentsUtil; +import net.oschina.app.improve.dialog.ShareDialog; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.improve.widget.TweetPicturesLayout; +import net.oschina.app.util.StringUtils; +import net.oschina.common.utils.StreamUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; + +/** + * 分享动弹界面 + * Created by huanghaibin on 2017/10/16. + */ + +public class TweetShareFragment extends BaseFragment implements Runnable { + + @Bind(R.id.iv_portrait) + PortraitView ivPortrait; + @Bind(R.id.tv_nick) + TextView tvNick; + @Bind(R.id.tv_time) + TextView tvTime; + @Bind(R.id.tweet_pics_layout) + TweetPicturesLayout mLayoutGrid; + + @Bind(R.id.tv_content) + TextView mTextContent; + @Bind(R.id.tv_ref_title) + TextView mViewRefTitle; + @Bind(R.id.tv_ref_content) + TextView mViewRefContent; + @Bind(R.id.layout_ref_images) + TweetPicturesLayout mLayoutRefImages; + @Bind(R.id.layout_ref) + LinearLayout mLayoutRef; + + @Bind(R.id.tv_tweet_like_count) + TextView mTextLikeCount; + + @Bind(R.id.tv_tweet_comment_count) + TextView mTextCommentCount; + + @Bind(R.id.tv_comment) + TextView mTextComment; + + @Bind(R.id.nsv_content) + NestedScrollView mViewScroller; + private ShareDialog mShareDialog; + private Bitmap mBitmap; + private ProgressDialog mDialog; + + @Bind(R.id.recyclerView) + RecyclerView mRecycleView; + private ShareCommentAdapter mAdapter; + + private ArrayList mComments; + private Tweet mTweet; + + public static TweetShareFragment newInstance(Tweet tweet, List comments) { + Bundle bundle = new Bundle(); + bundle.putSerializable("tweet", tweet); + if (comments != null && comments instanceof ArrayList) { + bundle.putSerializable("comments", (ArrayList) comments); + } + TweetShareFragment fragment = new TweetShareFragment(); + fragment.setArguments(bundle); + return fragment; + } + + @SuppressWarnings("all") + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + mTweet = (Tweet) bundle.getSerializable("tweet"); + mComments = (ArrayList) bundle.getSerializable("comments"); + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mDialog = DialogHelper.getProgressDialog(mContext); + mDialog.setMessage("请稍候..."); + mShareDialog = new ShareDialog(getActivity(), -1, false); + mRecycleView.setLayoutManager(new LinearLayoutManager(mContext)); + mAdapter = new ShareCommentAdapter(mContext, BaseRecyclerAdapter.NEITHER, mTweet); + + mRecycleView.setAdapter(mAdapter); + init(mTweet); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_tweet_share; + } + + + void initList(List list) { + if (mComments != null && mComments.size() > 0) { + mAdapter.resetItem(mComments); + return; + } + mAdapter.resetItem(list); + if (mContext == null) + return; + if (list == null || list.size() == 0) { + mTextComment.setVisibility(View.GONE); + } else { + mTextComment.setVisibility(View.VISIBLE); + } + } + + private void init(Tweet tweet) { + if (mContext == null) + return; + Author author = tweet.getAuthor(); + if (author != null) { + ivPortrait.setup(author); + tvNick.setText(author.getName()); + } else { + ivPortrait.setup(0, "匿名用户", ""); + tvNick.setText("匿名用户"); + } + if (!TextUtils.isEmpty(tweet.getPubDate())) + tvTime.setText(StringUtils.getDateString(tweet.getPubDate())); + if (!TextUtils.isEmpty(tweet.getContent())) { + String content = tweet.getContent().replaceAll("[\n\\s]+", " "); + CommentsUtil.formatHtml(mTextContent.getResources(), mTextContent, content, true, false); + } + + mLayoutGrid.setImage(tweet.getImages()); + + /* -- about reference -- */ + if (tweet.getAbout() != null) { + mLayoutRef.setVisibility(View.VISIBLE); + About about = tweet.getAbout(); + mLayoutRefImages.setImage(about.getImages()); + + if (!About.check(about)) { + mViewRefTitle.setVisibility(View.VISIBLE); + mViewRefTitle.setText("不存在或已删除的内容"); + mViewRefContent.setText("抱歉,该内容不存在或已被删除"); + } else { + if (about.getType() == OSChinaApi.COMMENT_TWEET) { + mViewRefTitle.setVisibility(View.GONE); + String con = "@" + about.getTitle() + ": " + about.getContent(); + CommentsUtil.formatHtml(mViewRefContent.getResources(), mViewRefContent, con, true, true); + } else { + mViewRefTitle.setVisibility(View.VISIBLE); + mViewRefTitle.setText(about.getTitle()); + mViewRefContent.setText(about.getContent()); + } + } + } else { + mLayoutRef.setVisibility(View.GONE); + } + mTextCommentCount.setText(String.valueOf(tweet.getCommentCount())); + mTextLikeCount.setText(String.valueOf(tweet.getLikeCount())); + } + + @Override + public void run() { + mBitmap = create(mViewScroller.getChildAt(0)); + mShareDialog.bitmap(mBitmap); + mDialog.dismiss(); + mShareDialog.show(); + } + + public void share() { + recycle(); + mDialog.show(); + mRoot.postDelayed(this, 2000); + mBitmap = create(mViewScroller.getChildAt(0)); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + public void save() { + recycle(); + mBitmap = create(mViewScroller.getChildAt(0)); + FileOutputStream os = null; + try { + String url = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + .getAbsolutePath() + File.separator + "开源中国/save/"; + File file = new File(url); + if (!file.exists()) { + file.mkdirs(); + } + String path = String.format("%s%s.jpg", url, System.currentTimeMillis()); + os = new FileOutputStream(path); + mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + os.flush(); + os.close(); + SimplexToast.show(mContext, "保存成功"); + Uri localUri = Uri.fromFile(new File(path)); + Intent localIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, localUri); + getActivity().sendBroadcast(localIntent); + } catch (Exception e) { + e.printStackTrace(); + SimplexToast.show(mContext, "保存失败"); + } finally { + StreamUtil.close(os); + recycle(); + } + } + + @Override + public void onPause() { + super.onPause(); + mShareDialog.dismiss(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + recycle(); + } + + private static Bitmap create(View v) { + try { + int w = v.getWidth(); + int h = v.getHeight(); + Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565); + Canvas c = new Canvas(bmp); + c.drawColor(Color.WHITE); + v.layout(0, 0, w, h); + v.draw(c); + return bmp; + } catch (OutOfMemoryError error) { + error.printStackTrace(); + return null; + } + } + + private void recycle() { + if (mBitmap != null && mBitmap.isRecycled()) { + mBitmap.recycle(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/widget/TweetPicturesPreviewer.java b/app/src/main/java/net/oschina/app/improve/tweet/widget/TweetPicturesPreviewer.java new file mode 100644 index 0000000000000000000000000000000000000000..1f80e12d0ef392431bc526810eb983aa50dd1107 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/widget/TweetPicturesPreviewer.java @@ -0,0 +1,98 @@ +package net.oschina.app.improve.tweet.widget; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.util.AttributeSet; +import android.view.View; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + +import net.oschina.app.improve.media.SelectImageActivity; +import net.oschina.app.improve.media.config.SelectOptions; +import net.oschina.app.improve.tweet.adapter.TweetSelectImageAdapter; + +/** + * Created by JuQiu + * on 16/7/18. + *

    + * 动弹发布界面, 图片预览器 + *

    + * 提供图片预览/图片操作 返回选中图片等功能 + */ + +public class TweetPicturesPreviewer extends RecyclerView implements TweetSelectImageAdapter.Callback { + private TweetSelectImageAdapter mImageAdapter; + private ItemTouchHelper mItemTouchHelper; + private RequestManager mCurImageLoader; + + public TweetPicturesPreviewer(Context context) { + super(context); + init(); + } + + public TweetPicturesPreviewer(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public TweetPicturesPreviewer(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + mImageAdapter = new TweetSelectImageAdapter(this); + + GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 3); + this.setLayoutManager(layoutManager); + this.setAdapter(mImageAdapter); + this.setOverScrollMode(View.OVER_SCROLL_NEVER); + + ItemTouchHelper.Callback callback = new TweetPicturesPreviewerItemTouchCallback(mImageAdapter); + mItemTouchHelper = new ItemTouchHelper(callback); + mItemTouchHelper.attachToRecyclerView(this); + } + + public void set(String[] paths) { + mImageAdapter.clear(); + for (String path : paths) { + mImageAdapter.add(path); + } + mImageAdapter.notifyDataSetChanged(); + } + + @Override + public void onLoadMoreClick() { + SelectImageActivity.show(getContext(), new SelectOptions.Builder() + .setHasCam(true) + .setSelectCount(9) + .setSelectedImages(mImageAdapter.getPaths()) + .setCallback(new SelectOptions.Callback() { + @Override + public void doSelected(String[] images) { + set(images); + } + }).build()); + } + + @Override + public RequestManager getImgLoader() { + if (mCurImageLoader == null) { + mCurImageLoader = Glide.with(getContext()); + } + return mCurImageLoader; + } + + @Override + public void onStartDrag(ViewHolder viewHolder) { + mItemTouchHelper.startDrag(viewHolder); + } + + public String[] getPaths() { + return mImageAdapter.getPaths(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/tweet/widget/TweetPicturesPreviewerItemTouchCallback.java b/app/src/main/java/net/oschina/app/improve/tweet/widget/TweetPicturesPreviewerItemTouchCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..838b46e05b308f83ebb4c15e7caf46ed1e05a265 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/tweet/widget/TweetPicturesPreviewerItemTouchCallback.java @@ -0,0 +1,164 @@ +package net.oschina.app.improve.tweet.widget; + +import android.graphics.Canvas; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * Created by JuQiu + * on 16/7/18. + *

    + * An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and + * swipe-to-dismiss. Drag events are automatically started by an item long-press.
    + *
    + * Expects the RecyclerView.Adapter to listen for {@link + * ItemTouchHelperAdapter} callbacks and the RecyclerView.ViewHolder to implement + * {@link ItemTouchHelperViewHolder}. + */ + +public class TweetPicturesPreviewerItemTouchCallback extends ItemTouchHelper.Callback { + + public static final float ALPHA_FULL = 1.0f; + + private final ItemTouchHelperAdapter mAdapter; + + public TweetPicturesPreviewerItemTouchCallback(ItemTouchHelperAdapter adapter) { + mAdapter = adapter; + } + + @Override + public boolean isLongPressDragEnabled() { + return false; + } + + @Override + public boolean isItemViewSwipeEnabled() { + return false; + } + + @Override + public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + // Set movement flags based on the layout manager + if (recyclerView.getLayoutManager() instanceof GridLayoutManager) { + final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; + final int swipeFlags = 0; + return makeMovementFlags(dragFlags, swipeFlags); + } else { + final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; + final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; + return makeMovementFlags(dragFlags, swipeFlags); + } + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType()) { + return false; + } + + // Notify the adapter of the move + mAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition()); + return true; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) { + // Notify the adapter of the dismissal + mAdapter.onItemDismiss(viewHolder.getAdapterPosition()); + } + + @Override + public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { + if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { + // Fade out the view as it is swiped out of the parent's bounds + final float alpha = ALPHA_FULL - Math.abs(dX) / (float) viewHolder.itemView.getWidth(); + viewHolder.itemView.setAlpha(alpha); + viewHolder.itemView.setTranslationX(dX); + } else { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + } + } + + @Override + public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { + // We only want the active item to change + if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { + if (viewHolder instanceof ItemTouchHelperViewHolder) { + // Let the view holder know that this item is being moved or dragged + ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemSelected(); + } + } + + super.onSelectedChanged(viewHolder, actionState); + } + + @Override + public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + + viewHolder.itemView.setAlpha(ALPHA_FULL); + + if (viewHolder instanceof ItemTouchHelperViewHolder) { + // Tell the view holder it's time to restore the idle state + ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemClear(); + } + } + + /** + * Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}. + */ + public interface ItemTouchHelperAdapter { + + /** + * Called when an item has been dragged far enough to trigger a move. This is called every time + * an item is shifted, and not at the end of a "drop" event.
    + *
    + * Implementations should call {@link RecyclerView.Adapter#notifyItemMoved(int, int)} after + * adjusting the underlying data to reflect this move. + * + * @param fromPosition The start position of the moved item. + * @param toPosition Then resolved position of the moved item. + * @return True if the item was moved to the new adapter position. + * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder) + * @see RecyclerView.ViewHolder#getAdapterPosition() + */ + boolean onItemMove(int fromPosition, int toPosition); + + + /** + * Called when an item has been dismissed by a swipe.
    + *
    + * Implementations should call {@link RecyclerView.Adapter#notifyItemRemoved(int)} after + * adjusting the underlying data to reflect this removal. + * + * @param position The position of the item dismissed. + * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder) + * @see RecyclerView.ViewHolder#getAdapterPosition() + */ + void onItemDismiss(int position); + } + + + /** + * Interface to notify an item ViewHolder of relevant callbacks from {@link + * android.support.v7.widget.helper.ItemTouchHelper.Callback}. + */ + public interface ItemTouchHelperViewHolder { + + /** + * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped. + * Implementations should update the item view to indicate it's active state. + */ + void onItemSelected(); + + + /** + * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item + * state should be cleared. + */ + void onItemClear(); + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/user/activities/OnFriendSelector.java b/app/src/main/java/net/oschina/app/improve/user/activities/OnFriendSelector.java new file mode 100644 index 0000000000000000000000000000000000000000..a514baa2659c106bd4afea62f8dee40fca2b027d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/activities/OnFriendSelector.java @@ -0,0 +1,18 @@ +package net.oschina.app.improve.user.activities; + +import net.oschina.app.improve.user.bean.UserFriend; + +/** + * Created by fei + * on 2016/12/29. + * desc: + */ + +public interface OnFriendSelector { + + void select(UserFriend userFriend); + + void unSelect(UserFriend userFriend); + + void selectFull(int selectCount); +} diff --git a/app/src/main/java/net/oschina/app/improve/user/activities/OnSelectFriendListener.java b/app/src/main/java/net/oschina/app/improve/user/activities/OnSelectFriendListener.java new file mode 100644 index 0000000000000000000000000000000000000000..0dccebe3c86e641786dbc88825ac4aa149cc5fbf --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/activities/OnSelectFriendListener.java @@ -0,0 +1,18 @@ +package net.oschina.app.improve.user.activities; + +import net.oschina.app.improve.user.bean.UserFriend; + +/** + * Created by fei + * on 2016/12/29. + * desc: + */ + +public interface OnSelectFriendListener { + + void select(UserFriend userFriend); + + void unSelect(UserFriend userFriend); + + void selectFull(int selectCount); +} diff --git a/app/src/main/java/net/oschina/app/improve/user/activities/OtherUserHomeActivity.java b/app/src/main/java/net/oschina/app/improve/user/activities/OtherUserHomeActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..e9968878c486f641c86dc352b8d60440a3c3a3fe --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/activities/OtherUserHomeActivity.java @@ -0,0 +1,609 @@ +package net.oschina.app.improve.user.activities; + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Rect; +import android.os.Build; +import android.os.Bundle; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.widget.Toolbar; +import android.text.TextUtils; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BaseActivity; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.UserRelation; +import net.oschina.app.improve.media.ImageGalleryActivity; +import net.oschina.app.improve.tweet.fragments.TweetFragment; +import net.oschina.app.improve.user.data.UserDataActivity; +import net.oschina.app.improve.user.fragments.UserActiveFragment; +import net.oschina.app.improve.user.fragments.UserBlogFragment; +import net.oschina.app.improve.user.fragments.UserQuestionFragment; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.improve.widget.SolarSystemView; +import net.oschina.app.util.UIHelper; +import net.oschina.app.widget.AvatarView; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * 别的用户的主页 + * Created by thanatos on 16/7/13. + * Updated by jzz on 2017/02/09 + */ +public class OtherUserHomeActivity extends BaseActivity + implements View.OnClickListener, DialogInterface.OnClickListener { + + public static final String KEY_BUNDLE = "KEY_BUNDLE_IN_OTHER_USER_HOME"; + + /* 谁格式化了我这里的代码我就打谁 */ + @Bind(R.id.toolbar) + Toolbar mToolbar; + @Bind(R.id.iv_portrait) + PortraitView mPortrait; + @Bind(R.id.tv_nick) + TextView mNick; + @Bind(R.id.tv_summary) + TextView mSummary; + @Bind(R.id.tv_avail_score) + TextView mTextAvailScore; + @Bind(R.id.tv_active_score) + TextView mTextActiveScore; + @Bind(R.id.tv_count_follow) + TextView mCountFollow; + @Bind(R.id.tv_count_fans) + TextView mCountFans; + @Bind(R.id.view_solar_system) + SolarSystemView mSolarSystem; + @Bind(R.id.layout_appbar) + AppBarLayout mLayoutAppBar; + @Bind(R.id.iv_logo_portrait) + PortraitView mLogoPortrait; + @Bind(R.id.tv_logo_nick) + TextView mLogoNick; + @Bind(R.id.iv_gender) + ImageView mGenderImage; + @Bind(R.id.layout_tab) + TabLayout mTabLayout; + @Bind(R.id.view_pager) + ViewPager mViewPager; + @Bind(R.id.view_divider) + View mDivider; + @Bind(R.id.identityView) + IdentityView mIdentityView; + + private boolean mIsLoadSuccess = false; + private User user; + private MenuItem mFollowMenu; + private List> fragments; + private TabLayoutOffsetChangeListener mOffsetChangerListener; + + public static void show(Context context, Author author) { + if (author == null) return; + User user = new User(); + user.setId(author.getId()); + user.setName(author.getName()); + user.setPortrait(author.getPortrait()); + show(context, user); + } + + public static void show(Context context, long id) { + if (id <= 0) return; + User user = new User(); + user.setId((int) id); + show(context, user); + } + + public static void show(Context context, String nick) { + if (TextUtils.isEmpty(nick)) return; + User user = new User(); + user.setName(nick); + show(context, user); + } + + /** + * @param context context + * @param id 无效值,随便填,只是用来区别{{@link #show(Context, String)}}方法的 + * @param suffix 个性后缀 + */ + @SuppressWarnings("unused") + public static void show(Context context, long id, String suffix) { + if (TextUtils.isEmpty(suffix)) return; + User user = new User(); + user.setSuffix(suffix); + show(context, user); + } + + public static void show(Context context, User user) { + if (user == null) return; + Intent intent = new Intent(context, OtherUserHomeActivity.class); + intent.putExtra(KEY_BUNDLE, user); + context.startActivity(intent); + } + + @Override + protected boolean initBundle(Bundle bundle) { + user = (User) bundle.getSerializable(KEY_BUNDLE); + if (user == null || (user.getId() <= 0 && TextUtils.isEmpty(user.getName()) + && TextUtils.isEmpty(user.getSuffix()))) { + Toast.makeText(this, "没有此用户", Toast.LENGTH_SHORT).show(); + return false; + } + return super.initBundle(bundle); + } + + @Override + protected int getContentView() { + return R.layout.activity_other_user_home; + } + + @Override + protected void initWidget() { + super.initWidget(); + mToolbar.setTitle(""); + mToolbar.setSubtitle(""); + mToolbar.setNavigationIcon(R.mipmap.btn_back_normal); + setSupportActionBar(mToolbar); + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + + mLayoutAppBar.addOnOffsetChangedListener(mOffsetChangerListener = new TabLayoutOffsetChangeListener()); + + mPortrait.post(new Runnable() { + @Override + public void run() { + int mStatusBarHeight = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Rect rectangle = new Rect(); + Window window = getWindow(); + window.getDecorView().getWindowVisibleDisplayFrame(rectangle); + mStatusBarHeight = rectangle.top; + } + float x = mPortrait.getX(); + float y = mPortrait.getY(); + ViewGroup parent = (ViewGroup) mPortrait.getParent(); + float px = x + parent.getX() + mPortrait.getWidth() / 2; + float py = y + parent.getY() + mPortrait.getHeight() / 2 + mStatusBarHeight; + int mMaxRadius = (int) (mSolarSystem.getHeight() - py + 50); + + int r = mPortrait.getWidth() / 2; + Random random = new Random(System.currentTimeMillis()); + for (int i = 60, radius = r + i; ; i = (int) (i * 1.4), radius += i) { + SolarSystemView.Planet planet = new SolarSystemView.Planet(); + planet.setClockwise(random.nextInt(10) % 2 == 0); + planet.setAngleRate((random.nextInt(35) + 1) / 1000.f); + planet.setRadius(radius); + mSolarSystem.addPlanets(planet); + if (radius > mMaxRadius) break; + } + mSolarSystem.setPivotPoint(px, py); + float ry = mSolarSystem.getHeight() - py; + double rx = Math.pow(px * px + ry * ry, 1.f / 2.f); + mSolarSystem.setRadialGradient(px, py, (float) rx, 0XFF24CF5F, 0XFF20B955); + } + }); + + injectDataToView(); + } + + private boolean isLoadSuccess() { + return mIsLoadSuccess && user != null && user.getId() > 0; + } + + @SuppressWarnings("all") + private void injectDataToViewPager() { + if (!isLoadSuccess()) return; + + mTabLayout.addTab(mTabLayout.newTab().setCustomView(getTabView("0", "动弹"))); + mTabLayout.addTab(mTabLayout.newTab().setCustomView(getTabView("0", "博客"))); + mTabLayout.addTab(mTabLayout.newTab().setCustomView(getTabView("0", "问答"))); + mTabLayout.addTab(mTabLayout.newTab().setCustomView(getTabView("0", "讨论"))); + mTabLayout.setVisibility(View.VISIBLE); + + int t = 0, b = 0, a = 0, d = 0; + if (user.getStatistics() != null) { + t = user.getStatistics().getTweet(); + b = user.getStatistics().getBlog(); + a = user.getStatistics().getAnswer(); + d = user.getStatistics().getDiscuss(); + } + + if (fragments == null) { + fragments = new ArrayList<>(); + fragments.add(new Pair<>( + String.format("%s\n动弹", 0), + TweetFragment.instantiate(user.getId(), 0, true))); + fragments.add(new Pair<>( + String.format("%s\n博客", 0), + UserBlogFragment.instantiate(user.getId()))); + fragments.add(new Pair<>( + String.format("%s\n问答", 0), + UserQuestionFragment.instantiate((int) user.getId()))); + fragments.add(new Pair<>( + String.format("%s\n讨论", 0), + UserActiveFragment.instantiate(user.getId()))); + + mViewPager.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) { + @Override + public Fragment getItem(int position) { + return fragments.get(position).second; + } + + @Override + public int getCount() { + return fragments.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + return fragments.get(position).first; + } + }); + + mTabLayout.setupWithViewPager(mViewPager); + // TabLayout will remove up all tabs after setted up view pager + // so we set it again + mTabLayout.getTabAt(0).setCustomView(getTabView(formatNumeric(t), "动弹")); + mTabLayout.getTabAt(1).setCustomView(getTabView(formatNumeric(b), "博客")); + mTabLayout.getTabAt(2).setCustomView(getTabView(formatNumeric(a), "问答")); + mTabLayout.getTabAt(3).setCustomView(getTabView(formatNumeric(d), "讨论")); + } else { // when request user detail info successfully + setupTabText(mTabLayout.getTabAt(0), t); + setupTabText(mTabLayout.getTabAt(1), b); + setupTabText(mTabLayout.getTabAt(2), a); + setupTabText(mTabLayout.getTabAt(3), d); + } + } + + @SuppressWarnings("all") + private void setupTabText(TabLayout.Tab tab, int count) { + View view = tab.getCustomView(); + if (view == null) return; + TabViewHolder holder = (TabViewHolder) view.getTag(); + holder.mViewCount.setText(formatNumeric(count)); + } + + private String formatNumeric(int count) { + if (count > 1000) { + int a = count / 100; + int b = a % 10; + int c = a / 10; + String str; + if (c <= 9 && b != 0) str = c + "." + b; + else str = String.valueOf(c); + return str + "k"; + } else { + return String.valueOf(count); + } + } + + private View getTabView(String cs, String tag) { + View view = LayoutInflater.from(this).inflate(R.layout.tab_item_other_user, mTabLayout, false); + TabViewHolder holder = new TabViewHolder(view); + holder.mViewCount.setText(cs); + holder.mViewTag.setText(tag); + view.setTag(holder); + return view; + } + + private void injectDataToView() { + if (user == null + || user.getId() == 0 + || user.getName() == null) + return; + + mPortrait.setup(user); + mPortrait.setOnClickListener(this); + mLogoPortrait.setup(user); + mIdentityView.setup(user); + + mLogoNick.setText(user.getName()); + mNick.setText(user.getName()); + String desc = user.getDesc(); + mSummary.setText(TextUtils.isEmpty(desc) ? "这人很懒,什么都没写" : desc); + if (user.getStatistics() != null) { + mTextAvailScore.setText(String.format("技能积分 %s", user.getStatistics().getHonorScore())); + mTextActiveScore.setText(String.format("活跃积分 %s", user.getStatistics().getActiveScore())); + mCountFans.setText(String.format("粉丝 %s", user.getStatistics().getFans())); + mCountFollow.setText(String.format("关注 %s", user.getStatistics().getFollow())); + } else { + mTextAvailScore.setText("技能积分 0"); + mTextActiveScore.setText("活跃积分 0"); + mCountFans.setText("粉丝 0"); + mCountFollow.setText("关注 0"); + } + + mGenderImage.setVisibility(View.VISIBLE); + if (user.getGender() == User.GENDER_MALE) { + mGenderImage.setImageResource(R.mipmap.ic_male); + } else if (user.getGender() == User.GENDER_FEMALE) { + mGenderImage.setImageResource(R.mipmap.ic_female); + } else { + mGenderImage.setVisibility(View.GONE); + } + + mOffsetChangerListener.resetRange(); + } + + @Override + protected void initData() { + super.initData(); + + final ProgressDialog dialog = DialogHelper.getProgressDialog(this, "正在获取用户数据..."); + dialog.setCanceledOnTouchOutside(false); + dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + onBackPressed(); + } + }); + OSChinaApi.getUserInfo(user.getId(), user.getName(), user.getSuffix(), new TextHttpResponseHandler() { + @Override + public void onStart() { + super.onStart(); + dialog.show(); + } + + @Override + public void onFinish() { + super.onFinish(); + dialog.dismiss(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (isFinishing() || isDestroyed()) + return; + Toast.makeText(OtherUserHomeActivity.this, "获取用户信息失败", Toast.LENGTH_SHORT).show(); + } + + @SuppressWarnings("RestrictedApi") + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (isFinishing() || isDestroyed()) + return; + + try { + ResultBean result = AppOperator.createGson().fromJson( + responseString, new TypeToken>() { + }.getType()); + if (result.isSuccess() && result.getResult() == null) return; + user = result.getResult(); + if (user == null || user.getId() == 0) { + Toast.makeText(OtherUserHomeActivity.this, "该用户不存在", Toast.LENGTH_SHORT).show(); + finish(); + return; + } + mIsLoadSuccess = true; + // 再次初始化用户信息 + injectDataToView(); + injectDataToViewPager(); + // 成功后初始化菜单 + invalidateOptionsMenu(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + @SuppressWarnings("deprecation") + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if (isLoadSuccess() && AccountHelper.isLogin() && AccountHelper.getUserId() != user.getId()) { + getMenuInflater().inflate(R.menu.menu_other_user, menu); + mFollowMenu = menu.getItem(1); + if (mFollowMenu == null) return false; + switch (user.getRelation()) { + case User.RELATION_TYPE_BOTH: + mFollowMenu.setIcon(getResources().getDrawable( + R.drawable.selector_user_following_botn)); + break; + case User.RELATION_TYPE_ONLY_FANS_HIM: + mFollowMenu.setIcon(getResources().getDrawable( + R.drawable.selector_user_following)); + break; + case User.RELATION_TYPE_ONLY_FANS_ME: + mFollowMenu.setIcon(getResources().getDrawable( + R.drawable.selector_user_follow)); + break; + case User.RELATION_TYPE_NULL: + mFollowMenu.setIcon(getResources().getDrawable( + R.drawable.selector_user_follow)); + break; + default: + mFollowMenu.setIcon(getResources().getDrawable( + R.drawable.selector_user_follow)); + } + return true; + } + return false; + } + + @Override + public void onClick(View v) { + if (!isLoadSuccess()) + return; + switch (v.getId()) { + case R.id.tv_count_follow: + UserFollowsActivity.show(this, user.getId()); + break; + case R.id.tv_count_fans: + UserFansActivity.show(this, user.getId()); + break; + case R.id.view_solar_system: +// Bundle userBundle = new Bundle(); +// userBundle.putSerializable("user_info", user); +// UIHelper.showSimpleBack(this, SimpleBackPage.MY_INFORMATION_DETAIL, userBundle); + UserDataActivity.show(this, user); + break; + case R.id.iv_portrait: + String url; + if (user == null || TextUtils.isEmpty(url = user.getPortrait())) return; + url = AvatarView.getLargeAvatar(url); + ImageGalleryActivity.show(this, url); + break; + } + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_pm: + if (user.getId() == AccountHelper.getUserId()) { + AppContext.showToast("不能给自己发送留言:)"); + return true; + } + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(this); + return true; + } + UserSendMessageActivity.show(this, user); + break; + case R.id.menu_follow: + // 判断登录 + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(this); + return true; + } + String mDialogTitle; + switch (user.getRelation()) { + case User.RELATION_TYPE_BOTH: + mDialogTitle = "确定取消互粉吗?"; + break; + case User.RELATION_TYPE_ONLY_FANS_HIM: + mDialogTitle = "确定取消关注吗?"; + break; + case User.RELATION_TYPE_ONLY_FANS_ME: + mDialogTitle = "确定关注Ta吗?"; + break; + case User.RELATION_TYPE_NULL: + mDialogTitle = "确定关注Ta吗?"; + break; + default: + return false; + } + DialogHelper.getConfirmDialog(this, mDialogTitle, this).show(); + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (mFollowMenu == null) return; + OSChinaApi.addUserRelationReverse(user.getId(), new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString + , Throwable throwable) { + Toast.makeText(OtherUserHomeActivity.this, "操作失败", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + ResultBean result = AppOperator.createGson().fromJson( + responseString, new TypeToken>() { + }.getType()); + if (result.isSuccess()) { + int relation = result.getResult().getRelation(); + switch (relation) { + case User.RELATION_TYPE_BOTH: + mFollowMenu.setIcon(getResources().getDrawable( + R.drawable.selector_user_following_botn)); + break; + case User.RELATION_TYPE_ONLY_FANS_HIM: + mFollowMenu.setIcon(getResources().getDrawable( + R.drawable.selector_user_following)); + break; + case User.RELATION_TYPE_ONLY_FANS_ME: + mFollowMenu.setIcon(getResources().getDrawable( + R.drawable.selector_user_follow)); + break; + case User.RELATION_TYPE_NULL: + mFollowMenu.setIcon(getResources().getDrawable( + R.drawable.selector_user_follow)); + break; + } + user.setRelation(relation); + } else { + onFailure(statusCode, headers, responseString, null); + } + } + }); + } + + private static class TabViewHolder { + private TextView mViewCount; + private TextView mViewTag; + + private TabViewHolder(View view) { + mViewCount = (TextView) view.findViewById(R.id.tv_count); + mViewTag = (TextView) view.findViewById(R.id.tv_tag); + } + } + + private class TabLayoutOffsetChangeListener implements AppBarLayout.OnOffsetChangedListener { + boolean isShow = false; + int mScrollRange = -1; + + @Override + public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { + if (mScrollRange == -1) { + mScrollRange = appBarLayout.getTotalScrollRange(); + } + if (mScrollRange + verticalOffset == 0) { + mLogoNick.setVisibility(View.VISIBLE); + mLogoPortrait.setVisibility(View.VISIBLE); + mDivider.setVisibility(View.GONE); + isShow = true; + } else if (isShow) { + mLogoNick.setVisibility(View.GONE); + mLogoPortrait.setVisibility(View.GONE); + mDivider.setVisibility(View.VISIBLE); + isShow = false; + } + mTabLayout.getBackground().setAlpha(Math.round(255 - Math.abs(verticalOffset) / (float) mScrollRange * 255)); + } + + private void resetRange() { + mScrollRange = -1; + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/user/activities/UserBlogActivity.java b/app/src/main/java/net/oschina/app/improve/user/activities/UserBlogActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..c3e363ec0487c27dc617861f5bec2ac3b9c55bd1 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/activities/UserBlogActivity.java @@ -0,0 +1,35 @@ +package net.oschina.app.improve.user.activities; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.user.fragments.UserBlogFragment; + +/** + * 用户博客 + * Created by huanghaibin on 2017/10/30. + */ + +public class UserBlogActivity extends BackActivity { + + public static void show(Context context, long id) { + Intent intent = new Intent(context, UserBlogActivity.class); + intent.putExtra(UserBlogFragment.BUNDLE_KEY_USER_ID, id); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_user_blog; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + addFragment(R.id.fl_content,UserBlogFragment.instantiate(getIntent().getLongExtra(UserBlogFragment.BUNDLE_KEY_USER_ID,0))); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/activities/UserEventSigninActivity.java b/app/src/main/java/net/oschina/app/improve/user/activities/UserEventSigninActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..a2e646303d07a5ca3e11a47d5ea099f837d63c84 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/activities/UserEventSigninActivity.java @@ -0,0 +1,534 @@ +package net.oschina.app.improve.user.activities; + +import android.annotation.SuppressLint; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.StringRes; +import android.text.Editable; +import android.text.TextUtils; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BaseBackActivity; +import net.oschina.app.improve.bean.Event; +import net.oschina.app.improve.bean.EventDetail; +import net.oschina.app.improve.bean.EventSignIn; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.user.sign.in.SignInInfoActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.utils.parser.RichTextParser; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.util.TDevice; +import net.oschina.common.adapter.TextWatcherAdapter; + +import java.util.Map; +import java.util.Set; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * Created by fei + * on 2016/11/30. + * change by fei + * on 2016/12/15 + * desc:活动签到 + */ + +public class UserEventSigninActivity extends BaseBackActivity { + + public static final String EVENT_ID_KEY = "event_id_key"; + + @Bind(R.id.iv_event_img) + ImageView mIvImg; + @Bind(R.id.tv_event_title) + TextView mTvTitle; + @Bind(R.id.tv_event_author) + TextView mTvAuthor; + @Bind(R.id.tv_event_type) + TextView mTvType; + + @Bind(R.id.ck_check) + CheckBox mCkLabel; + + @Bind(R.id.line) + View mLine; + + @Bind(R.id.lay_input_bg) + LinearLayout mLayInputBg; + + @Bind(R.id.et_signin) + EditText mEtSignin; + + @Bind(R.id.lay_container_user_info) + LinearLayout mLayUserInfo; + + @Bind(R.id.tv_signin_notice) + TextView mTvNotice; + + @Bind(R.id.tv_cost_notice) + TextView mTvCost; + + @Bind(R.id.bt_signin_submit) + Button mBtSubmit; + + @Bind(R.id.lay_error) + EmptyLayout mEmptyLayout; + + private long mId; + private int mRequestType = 0x01;//0x01 请求活动详情 0x02 匹配当前账户信息是否是报名账户 0x03 签到 + private ProgressDialog mDialog; + + private EventDetail mEventDetail = null; + + /** + * show signinActivity + * + * @param context context + */ + public static void show(Context context, long sourceId) { + Intent intent = new Intent(context, UserEventSigninActivity.class); + intent.putExtra(EVENT_ID_KEY, sourceId); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_main_signin; + } + + @Override + protected boolean initBundle(Bundle bundle) { + mId = bundle.getLong(EVENT_ID_KEY, 0); + return mId > 0; + } + + @SuppressWarnings("deprecation") + @Override + protected void initWidget() { + super.initWidget(); + + mEtSignin.addTextChangedListener(new TextWatcherAdapter() { + @SuppressWarnings("deprecation") + @Override + public void afterTextChanged(Editable s) { + String input = s.toString().trim(); + boolean machPhoneNum = RichTextParser.machPhoneNum(input); + mLayInputBg.setActivated(true); + if (machPhoneNum) { + mBtSubmit.setEnabled(true); + mLayInputBg.setBackgroundResource(R.drawable.bg_signin_input_ok); + } else { + if (s.length() <= 0) { + mLayInputBg.setBackgroundResource(R.drawable.bg_signin_input_ok); + } else { + mLayInputBg.setBackgroundResource(R.drawable.bg_signin_input_error); + } + mBtSubmit.setEnabled(false); + } + } + }); + + mEmptyLayout.setOnLayoutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + EmptyLayout emptyLayout = mEmptyLayout; + if (emptyLayout != null && emptyLayout.getErrorState() != EmptyLayout.HIDE_LAYOUT) { + emptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + requestData(); + } + } + }); + + mBtSubmit.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + submitToSignIn(); + } + }); + + mIvImg.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + net.oschina.app.improve.detail.general.EventDetailActivity.show(UserEventSigninActivity.this, mId); + } + }); + } + + @SuppressWarnings("deprecation") + @Override + protected void initData() { + super.initData(); + + //检查网络 + if (!checkNetIsAvailable()) { + showError(EmptyLayout.NETWORK_ERROR); + return; + } + + requestData(); + } + + private boolean checkNetIsAvailable() { + if (!TDevice.hasInternet()) { + AppContext.showToastShort(getString(R.string.tip_network_error)); + showError(EmptyLayout.NETWORK_ERROR); + return false; + } + return true; + } + + private void requestData() { + int requestType = mRequestType; + switch (requestType) { + case 0x01: + requestEventDetail(mId); + break; + case 0x02: + requestApplyInfo(mEventDetail, mId); + break; + default: + break; + } + } + + private void submitToSignIn() { + String phone = mEtSignin.getText().toString().trim(); + OSChinaApi.eventSignin(mId, phone, new TextHttpResponseHandler() { + @Override + public void onStart() { + super.onStart(); + showFocusWaitDialog(R.string.state_submit); + } + + @Override + public void onFinish() { + super.onFinish(); + hideWaitDialog(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + AppContext.showToastShort(R.string.state_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + //签到成功更新数据 + ResultBean signinResultBean = AppOperator.createGson().fromJson(responseString, + new TypeToken>() { + }.getType()); + if (signinResultBean.isSuccess()) { + EventSignIn eventSignin = signinResultBean.getResult(); + updateSigninView(eventSignin); + } + } + }); + } + + private void requestEventDetail(final long sourceId) { + //1.第一次初始化活动详情数据 + OSChinaApi.getEventDetail(sourceId, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + showError(EmptyLayout.NETWORK_ERROR); + } + + @SuppressWarnings("deprecation") + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, + new TypeToken>() { + }.getType()); + + if (resultBean.isSuccess()) { + EventDetail eventDetail = resultBean.getResult(); + + if (eventDetail.getId() <= 0) { + AppContext.showToastShort(getString(R.string.event_null_hint)); + showError(EmptyLayout.NODATA); + return; + } + + mEventDetail = eventDetail; + + if (AccountHelper.isLogin()) { + if (!checkNetIsAvailable()) return; + mRequestType = 0x02; + //2.如果是登录状态,需要匹配是否是该账户的报名信息 + requestApplyInfo(eventDetail, sourceId); + } else { + mRequestType = 0x03; + updateDetailView(eventDetail); + mLayInputBg.setActivated(true); + mBtSubmit.setEnabled(false); + hideLoading(); + } + } else { + showError(EmptyLayout.NODATA); + } + } + }); + } + + private void requestApplyInfo(final EventDetail eventDetail, long sourceId) { + OSChinaApi.syncSignUserInfo(sourceId, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + showError(EmptyLayout.NETWORK_ERROR); + } + + @SuppressWarnings("deprecation") + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + + ResultBean> mapResultBean = AppOperator.createGson().fromJson(responseString, + new TypeToken>>() { + }.getType()); + + mRequestType = 0x03; + int code = mapResultBean.getCode(); + + switch (code) { + case 0: + //code=0,请求不到相关数据,直接使用手机号报名(ps:有可能使用的是其他账户或者直接手机号报名的情况) + updateDetailView(eventDetail); + setTelVisible(View.VISIBLE); + mLayInputBg.setActivated(true); + mBtSubmit.setEnabled(false); + hideLoading(); + break; + case 1: + //code=1,请求成功 + Map userInfoMap = mapResultBean.getResult(); + if (userInfoMap != null && userInfoMap.size() > 0) { + //显示相关报名的用户数据 + updateDetailView(eventDetail); + setTelVisible(View.GONE); + updateUserInfoView(userInfoMap); + mBtSubmit.setEnabled(true); + hideLoading(); + } else { + //报名所填相关用户数据全为null,但是却是报名了的 + updateDetailView(eventDetail); + setTelVisible(View.GONE); + mBtSubmit.setEnabled(true); + hideLoading(); + } + break; + case 404: + //code=404,当前登录的用户未报名该活动,返回相关数据为null + updateDetailView(eventDetail); + setTelVisible(View.VISIBLE); + mLayInputBg.setActivated(true); + mBtSubmit.setEnabled(false); + hideLoading(); + AppContext.showToastShort(mapResultBean.getMessage()); + break; + default: + AppContext.showToastShort(mapResultBean.getMessage()); + showError(EmptyLayout.NODATA); + break; + } + } + }); + } + + private void setTelVisible(int visible) { + mCkLabel.setVisibility(visible); + mLine.setVisibility(View.INVISIBLE); + mLayInputBg.setVisibility(visible); + } + + @SuppressLint("DefaultLocale") + private void updateDetailView(EventDetail eventDetail) { + + if (eventDetail.getImg() != null) + getImageLoader().load(eventDetail.getImg()).into(mIvImg); + + mTvTitle.setText(eventDetail.getTitle()); + + if (eventDetail.getAuthor() != null) + mTvAuthor.setText(String.format("%s %s", getString(R.string.signin_event_author), eventDetail.getAuthor())); + + int typeStr = R.string.osc_site; + + switch (eventDetail.getType()) { + case Event.EVENT_TYPE_OSC: + typeStr = R.string.event_type_osc; + break; + case Event.EVENT_TYPE_TEC: + typeStr = R.string.event_type_tec; + break; + case Event.EVENT_TYPE_OTHER: + typeStr = R.string.event_type_other; + break; + case Event.EVENT_TYPE_OUTSIDE: + typeStr = R.string.event_type_outside; + break; + } + + mTvType.setText(String.format("%s:%s", getString(R.string.signin_event_type_hint), getString(typeStr))); + + if (!AccountHelper.isLogin()) { + setTelVisible(View.VISIBLE); + } else { + setTelVisible(View.GONE); + } + } + + + private void updateUserInfoView(Map userInfo) { + + Set> entries = userInfo.entrySet(); + + for (Map.Entry next : entries) { + + String key = next.getKey(); + String value = next.getValue(); + + if (TextUtils.isEmpty(value) || TextUtils.isEmpty(key)) { + continue; + } + + @SuppressLint("InflateParams") View rootView = getLayoutInflater().inflate(R.layout.lay_signin_user_info, null, false); + TextView tvKey = (TextView) rootView.findViewById(R.id.tv_key); + tvKey.setText(String.format("%s:", key)); + TextView tvValue = (TextView) rootView.findViewById(R.id.tv_value); + tvValue.setText(value); + mLayUserInfo.addView(rootView); + } + } + + + /** + * update event signin view + * + * @param eventSignin eventSignin + */ + private void updateSigninView(EventSignIn eventSignin) { + int optStatus = eventSignin.getOptStatus(); + switch (optStatus) { + case 0x01://签到成功 + SubBean bean = new SubBean(); + bean.setTitle(mEventDetail.getTitle()); + SignInInfoActivity.show(this,bean,eventSignin); + finish(); +// return; +// mBtSubmit.setEnabled(false); +// mCkLabel.setVisibility(View.GONE); +// mLayInputBg.setVisibility(View.GONE); +// mLayUserInfo.setVisibility(View.GONE); +// mTvNotice.setVisibility(View.VISIBLE); +// mTvNotice.setText(eventSignin.getMessage()); +// if (!TextUtils.isEmpty(eventSignin.getCostMessage())) { +// mTvCost.setVisibility(View.VISIBLE); +// mTvCost.setText(eventSignin.getCostMessage()); +// } + break; + case 0x03://活动已结束/活动报名已截止 + case 0x04://您已签到 + SubBean bean1 = new SubBean(); + bean1.setTitle(mEventDetail.getTitle()); + SignInInfoActivity.show(this,bean1,eventSignin); + finish(); + +// mCkLabel.setVisibility(View.GONE); +// mLayInputBg.setVisibility(View.GONE); +// mTvNotice.setVisibility(View.VISIBLE); +// mTvNotice.setText(eventSignin.getMessage()); +// mLayUserInfo.setVisibility(View.GONE); +// mBtSubmit.setEnabled(false); +// if (!TextUtils.isEmpty(eventSignin.getCostMessage())) { +// mTvCost.setVisibility(View.VISIBLE); +// mTvCost.setText(eventSignin.getCostMessage()); +// } + break; + case 0x02://活动进行中未报名 + AppContext.showToastShort(eventSignin.getMessage()); + break; + default: + break; + } + } + + public void hideLoading() { + final EmptyLayout emptyLayout = mEmptyLayout; + if (emptyLayout == null) + return; + Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_alpha_to_hide); + animation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + + } + + @Override + public void onAnimationEnd(Animation animation) { + emptyLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }); + emptyLayout.startAnimation(animation); + } + + private void showError(int type) { + EmptyLayout layout = mEmptyLayout; + if (layout != null) { + layout.setErrorType(type); + } + } + + /** + * show FocusWaitDialog + * + * @return progressDialog + */ + private ProgressDialog showFocusWaitDialog(@StringRes int messageId) { + + String message = getResources().getString(messageId); + if (mDialog == null) { + mDialog = DialogHelper.getProgressDialog(this, message, false);//DialogHelp.getWaitDialog(this, message); + } + mDialog.show(); + + return mDialog; + } + + /** + * hide waitDialog + */ + private void hideWaitDialog() { + ProgressDialog dialog = mDialog; + if (dialog != null) { + mDialog = null; + try { + dialog.cancel(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/activities/UserFansActivity.java b/app/src/main/java/net/oschina/app/improve/user/activities/UserFansActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..7f697474cfd6a0aacb8b2e9ee6e23b4ae7df994d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/activities/UserFansActivity.java @@ -0,0 +1,105 @@ +package net.oschina.app.improve.user.activities; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.activities.BaseRecyclerViewActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.notice.NoticeBean; +import net.oschina.app.improve.notice.NoticeManager; +import net.oschina.app.improve.user.adapter.UserFansOrFollowAdapter; +import net.oschina.app.improve.user.bean.UserFansOrFollows; + +import java.lang.reflect.Type; + +/** + * Created by fei on 2016/8/24. + * desc: user fans + */ + +public class UserFansActivity extends BaseRecyclerViewActivity { + + public static final String BUNDLE_KEY_ID = "bundle_key_id"; + private long userId; + + + private int getRequestType() { + return OSChinaApi.TYPE_USER_FANS; + } + + /** + * show activity + * + * @param context context + * @param userId uid + */ + public static void show(Context context, long userId) { + Intent intent = new Intent(context, UserFansActivity.class); + intent.putExtra(BUNDLE_KEY_ID, userId); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_user_follows; + } + + @Override + protected boolean initBundle(Bundle bundle) { + userId = bundle.getLong(BUNDLE_KEY_ID, 0); + return super.initBundle(bundle); + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + } + + @Override + protected void onResume() { + super.onResume(); + + NoticeBean FansNotice = NoticeManager.getNotice(); + if (FansNotice == null) return; + int fans = FansNotice.getFans(); + if (fans > 0) { + NoticeManager.clear(this, NoticeManager.FLAG_CLEAR_FANS); + } + + } + + + @Override + protected void requestData() { + super.requestData(); + OSChinaApi.getUserFansOrFlows(getRequestType(), userId, mIsRefresh ? null : mBean.getNextPageToken(), mHandler); + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + return new UserFansOrFollowAdapter(this, BaseRecyclerAdapter.ONLY_FOOTER); + } + + @Override + protected void onItemClick(UserFansOrFollows item, int position) { + super.onItemClick(item, position); + if (item.getId() <= 0) return; + OtherUserHomeActivity.show(this, item.getId()); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/user/activities/UserFollowsActivity.java b/app/src/main/java/net/oschina/app/improve/user/activities/UserFollowsActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..f418e60c57457968cd04e85298415fcf50e7cf63 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/activities/UserFollowsActivity.java @@ -0,0 +1,90 @@ +package net.oschina.app.improve.user.activities; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.activities.BaseRecyclerViewActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.user.adapter.UserFansOrFollowAdapter; +import net.oschina.app.improve.user.bean.UserFansOrFollows; + +import java.lang.reflect.Type; + +/** + * Created by fei on 2016/8/24. + * desc: user follows + */ + +public class UserFollowsActivity extends BaseRecyclerViewActivity { + + public static final String BUNDLE_KEY_ID = "bundle_key_id"; + // private static final String TAG = "UserFollowsActivity"; + private long userId; + + + private int getRequestType() { + return OSChinaApi.TYPE_USER_FLOWS; + } + + /** + * show activity + * + * @param context context + * @param userId uid + */ + public static void show(Context context, long userId) { + Intent intent = new Intent(context, UserFollowsActivity.class); + intent.putExtra(BUNDLE_KEY_ID, userId); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_user_follows; + } + + @Override + protected boolean initBundle(Bundle bundle) { + userId = bundle.getLong(BUNDLE_KEY_ID, 0); + // TLog.e(TAG, "initBundle: ---------->userId=" + userId); + return super.initBundle(bundle); + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + } + + @Override + protected void requestData() { + super.requestData(); + OSChinaApi.getUserFansOrFlows(getRequestType(), userId, mIsRefresh ? null : mBean.getNextPageToken(), mHandler); + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + return new UserFansOrFollowAdapter(this, BaseRecyclerAdapter.ONLY_FOOTER); + } + + @Override + protected void onItemClick(UserFansOrFollows item, int position) { + super.onItemClick(item, position); + if (item.getId() <= 0) return; + OtherUserHomeActivity.show(this, item.getId()); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/activities/UserMessageActivity.java b/app/src/main/java/net/oschina/app/improve/user/activities/UserMessageActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..1bae74984b45719c2b2303d46004c1a0d0a9caf4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/activities/UserMessageActivity.java @@ -0,0 +1,205 @@ +package net.oschina.app.improve.user.activities; + +import android.content.Context; +import android.content.Intent; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.notice.NoticeBean; +import net.oschina.app.improve.notice.NoticeManager; +import net.oschina.app.improve.user.fragments.UserCommentFragment; +import net.oschina.app.improve.user.fragments.UserMentionFragment; +import net.oschina.app.improve.user.fragments.UserMessageFragment; + +import butterknife.Bind; + +/** + * Created by huanghaibin_dev + * Updated by Dominic Thanatosx + * on 2016/8/16. + * updated by fei + * on 2017/1/06 + */ +public class UserMessageActivity extends BackActivity implements NoticeManager.NoticeNotify { + + @Bind(R.id.tabLayout) + TabLayout mLayoutTab; + @Bind(R.id.vp_user_message) + ViewPager mViewPager; + + private static final int INDEX_MENTION = 0; + private static final int INDEX_COMMENT = 1; + private static final int INDEX_MESSAGE = 2; + + private UserMentionFragment mUserMentionFragment; + private UserCommentFragment mUserCommentFragment; + private UserMessageFragment mUserMessageFragment; + + private int mStatusLoading = 0X000; + + private NoticeBean mNotice; + private Runnable mNotifyAction; + + public static void show(Context context) { + context.startActivity(new Intent(context, UserMessageActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_user_message; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mNotice = NoticeManager.getNotice(); + + mUserMentionFragment = new UserMentionFragment(); + mUserCommentFragment = new UserCommentFragment(); + mUserMessageFragment = new UserMessageFragment(); + + NoticeManager.bindNotify(this); + + mLayoutTab.setupWithViewPager(mViewPager); + mViewPager.setAdapter(mAdapter); + + mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(int position) { + int i = (mStatusLoading & 0X1 << 4 * (2 - position)) >>> 4 * (2 - position); + if (i == 1) clearSpecificNoticeIfNecessary(position); + } + }); + + final int mCurrentViewIndex = getNotice().getMention() > 0 + ? INDEX_MENTION + : getNotice().getReview() > 0 + ? INDEX_COMMENT + : getNotice().getLetter() > 0 + ? INDEX_MESSAGE + : INDEX_MENTION; + + mViewPager.setCurrentItem(mCurrentViewIndex); + } + + private void clearSpecificNoticeIfNecessary(int position) { + switch (position) { + case INDEX_MENTION: + if (getNotice().getMention() <= 0) break; + clearSpecificNotice(position); + break; + case INDEX_COMMENT: + if (getNotice().getReview() <= 0) break; + clearSpecificNotice(position); + break; + case INDEX_MESSAGE: + if (getNotice().getLetter() <= 0) break; + clearSpecificNotice(position); + break; + } + } + + private void clearSpecificNotice(final int position) { + mViewPager.removeCallbacks(mNotifyAction); + Runnable notifyAction = new Runnable() { + @Override + public void run() { + switch (position) { + case INDEX_MENTION: + NoticeManager.clear(getApplicationContext(), NoticeManager.FLAG_CLEAR_MENTION); + break; + case INDEX_COMMENT: + NoticeManager.clear(getApplicationContext(), NoticeManager.FLAG_CLEAR_REVIEW); + break; + case INDEX_MESSAGE: + NoticeManager.clear(getApplicationContext(), NoticeManager.FLAG_CLEAR_LETTER); + break; + } + } + }; + mNotifyAction = notifyAction; + mViewPager.postDelayed(notifyAction, 2000); + } + + public void onRequestSuccess(int position) { + mStatusLoading |= 0X1 << (2 - position) * 4; + if (mViewPager.getCurrentItem() != position) return; + clearSpecificNoticeIfNecessary(position); + } + + private void updateTitle(int _old, int _new, int position) { + if (_old == _new) return; + + if (mViewPager.getCurrentItem() != position || _new != 0) { + mStatusLoading &= 0X111 ^ 0X1 << (2 - position) * 4; + } + + TabLayout.Tab tab = mLayoutTab.getTabAt(position); + if (tab == null) return; + tab.setText(mAdapter.getPageTitle(position)); + } + + private NoticeBean getNotice() { + if (mNotice == null) return new NoticeBean(); + return mNotice; + } + + @Override + public void onNoticeArrived(NoticeBean bean) { + NoticeBean nb = getNotice(); + mNotice = bean; + updateTitle(nb.getMention(), bean.getMention(), INDEX_MENTION); + updateTitle(nb.getReview(), bean.getReview(), INDEX_COMMENT); + updateTitle(nb.getLetter(), bean.getLetter(), INDEX_MESSAGE); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mNotice = null; + NoticeManager.unBindNotify(this); + NoticeManager.clear(getApplicationContext(), NoticeManager.FLAG_CLEAR_MENTION | + NoticeManager.FLAG_CLEAR_REVIEW | NoticeManager.FLAG_CLEAR_LETTER); + } + + private FragmentPagerAdapter mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) { + @Override + public Fragment getItem(int position) { + switch (position) { + case INDEX_MENTION: + return mUserMentionFragment; + case INDEX_COMMENT: + return mUserCommentFragment; + default: + return mUserMessageFragment; + } + } + + @Override + public int getCount() { + return 3; + } + + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case INDEX_MENTION: + return formatMessageCount("@我", getNotice().getMention()); + case INDEX_COMMENT: + return formatMessageCount("评论", getNotice().getReview()); + default: + return formatMessageCount("私信", getNotice().getLetter()); + } + } + }; + + private String formatMessageCount(String title, int messageCount) { + return messageCount == 0 ? title : String.format(title + "(%s)", messageCount); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/activities/UserSelectFriendsActivity.java b/app/src/main/java/net/oschina/app/improve/user/activities/UserSelectFriendsActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..29588e09355d672cf4ea544465777c594f7b9f5b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/activities/UserSelectFriendsActivity.java @@ -0,0 +1,548 @@ +package net.oschina.app.improve.user.activities; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.support.v4.app.Fragment; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.SearchView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnticipateOvershootInterpolator; +import android.widget.HorizontalScrollView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.bumptech.glide.Glide; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.improve.app.ParentLinkedHolder; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.tweet.fragments.TweetPublishFragment; +import net.oschina.app.improve.user.adapter.UserSearchFriendsAdapter; +import net.oschina.app.improve.user.adapter.UserSelectFriendsAdapter; +import net.oschina.app.improve.user.helper.ContactsCacheManager; +import net.oschina.app.improve.widget.RecentContactsView; +import net.oschina.app.improve.widget.RichEditText; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.util.TDevice; +import net.oschina.app.widget.IndexView; +import net.oschina.common.utils.CollectionUtil; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import butterknife.Bind; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + *

    + * 用户联系人列表 + */ +public class UserSelectFriendsActivity extends BackActivity + implements RecentContactsView.OnSelectedChangeListener, ContactsCacheManager.OnSelectedChangeListener, + IndexView.OnIndexTouchListener, SearchView.OnQueryTextListener { + + @Bind(R.id.searcher_friends) + SearchView mSearchView; + + @Bind(R.id.search_mag_icon) + ImageView mSearchIcon; + + @Bind(R.id.search_edit_frame) + LinearLayout mLayoutEditFrame; + + @Bind(R.id.recycler_friends_icon) + HorizontalScrollView mHorizontalScrollView; + + @Bind(R.id.select_container) + LinearLayout mSelectContainer; + + @Bind(R.id.tv_label) + TextView mTvLabel; + + @Bind(R.id.recycler_friends) + RecyclerView mRecyclerFriends; + + @Bind(R.id.tv_index_show) + TextView mTvIndexShow; + + @Bind(R.id.lay_index) + IndexView mIndex; + + @Bind(R.id.lay_error) + EmptyLayout mEmptyLayout; + + //选中数据 + private final LinkedList mSelectedFriends = new LinkedList<>(); + private final ArrayList mLocalFriends = new ArrayList<>(); + + // 最近联系人 + private RecentContactsView mRecentView; + //网络初始化的adapter + private UserSelectFriendsAdapter mLocalAdapter; + private UserSearchFriendsAdapter mSearchAdapter; + + private static ParentLinkedHolder textParentLinkedHolder; + + public static void show(Object starter, RichEditText editText) { + if (editText != null && (starter instanceof Activity || starter instanceof Fragment || starter instanceof android.app.Fragment)) { + synchronized (UserSelectFriendsActivity.class) { + ParentLinkedHolder holder = new ParentLinkedHolder<>(editText); + textParentLinkedHolder = holder.addParent(textParentLinkedHolder); + } + + if (starter instanceof Activity) { + Activity context = (Activity) starter; + Intent intent = new Intent(context, UserSelectFriendsActivity.class); + context.startActivityForResult(intent, TweetPublishFragment.REQUEST_CODE_SELECT_FRIENDS); + } else if (starter instanceof Fragment) { + Fragment fragment = (Fragment) starter; + Context context = fragment.getContext(); + if (context == null) + return; + Intent intent = new Intent(context, UserSelectFriendsActivity.class); + fragment.startActivityForResult(intent, TweetPublishFragment.REQUEST_CODE_SELECT_FRIENDS); + } else { + android.app.Fragment fragment = (android.app.Fragment) starter; + Context context = fragment.getActivity(); + if (context == null) + return; + Intent intent = new Intent(context, UserSelectFriendsActivity.class); + fragment.startActivityForResult(intent, TweetPublishFragment.REQUEST_CODE_SELECT_FRIENDS); + } + } + } + + @Override + protected int getContentView() { + return R.layout.activity_main_user_select_friends; + } + + @SuppressLint("ClickableViewAccessibility") + @SuppressWarnings("all") + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + // 初始化最近联系人 + mRecentView = new RecentContactsView(this); + mRecentView.setListener(this); + + //初始化searchView的搜索icon + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mSearchIcon.getLayoutParams(); + params.width = ViewGroup.LayoutParams.WRAP_CONTENT; + mSearchIcon.setLayoutParams(params); + + params = (LinearLayout.LayoutParams) mLayoutEditFrame.getLayoutParams(); + params.setMargins(0, 0, 0, 0); + mLayoutEditFrame.setLayoutParams(params); + mSearchView.setOnQueryTextListener(this); + + mEmptyLayout.setNoDataContent(getText(R.string.no_friend_hint).toString()); + mEmptyLayout.setOnLayoutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + EmptyLayout emptyLayout = mEmptyLayout; + if (emptyLayout != null && emptyLayout.getErrorState() != EmptyLayout.NETWORK_LOADING) { + emptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + initDataFromCacheOrNet(); + } + } + }); + + mRecyclerFriends.setLayoutManager(new LinearLayoutManager(this)); + + mRecyclerFriends.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + TDevice.hideSoftKeyboard(mSearchView); + return false; + } + }); + + mLocalAdapter = new UserSelectFriendsAdapter(mRecentView, this); + mSearchAdapter = new UserSearchFriendsAdapter(this, this, mSelectedFriends, mLocalFriends); + + mRecyclerFriends.setAdapter(mLocalAdapter); + mIndex.setOnIndexTouchListener(this); + mTvLabel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + sendSelectData(true); + } + }); + + // 初始化trigger + recentSelectedTrigger = mRecentView; + adapterSelectedTrigger = mLocalAdapter; + + //noinspection RestrictedApi + mSearchView.clearFocus(); + TDevice.hideSoftKeyboard(mSearchView); + } + + @Override + protected void initData() { + super.initData(); + initDataFromCacheOrNet(); + } + + /** + * 初始化数据 + */ + private void initDataFromCacheOrNet() { + ContactsCacheManager.sync(new Runnable() { + @Override + public void run() { + List authors = ContactsCacheManager.getContacts(); + final List friends = ContactsCacheManager.sortToFriendModel(authors); + runOnUiThread(new Runnable() { + @Override + public void run() { + displayFirstView(friends); + } + }); + } + }); + } + + private void displayFirstView(List friends) { + // 先进行清理 + mLocalFriends.clear(); + mLocalAdapter.getItems().clear(); + // 没有拉取到用户,但是有最近联系人也显示界面 + if ((friends != null && friends.size() > 0) || mRecentView.hasData()) { + // 有数据时 + if (friends != null) { + mLocalFriends.addAll(friends); + } + mLocalFriends.trimToSize(); + mLocalAdapter.initItems(friends); + } + + refreshLocalView(); + } + + private void refreshLocalView() { + if (mLocalAdapter.getItemCount() == 0) { + // 无数据时 + if (checkNetIsAvailable()) { + showError(EmptyLayout.NODATA); + } else { + showError(EmptyLayout.NETWORK_ERROR); + } + mIndex.setVisibility(View.GONE); + } else { + mIndex.setVisibility(View.VISIBLE); + showError(EmptyLayout.HIDE_LAYOUT); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_tweet_topic, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_submit) { + sendSelectData(false); + return true; + } + return super.onOptionsItemSelected(item); + } + + @SuppressWarnings("RestrictedApi") + @Override + protected void onStop() { + super.onStop(); + mSearchView.clearFocus(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + synchronized (UserSelectFriendsActivity.class) { + if (textParentLinkedHolder != null) { + textParentLinkedHolder = textParentLinkedHolder.putParent(); + } + } + } + + @Override + public void onIndexTouchUp() { + mTvIndexShow.setVisibility(View.GONE); + } + + @SuppressWarnings("EqualsBetweenInconvertibleTypes") + @Override + public void onIndexTouchMove(char indexLetter) { + String str = Character.toString(indexLetter); + List friends = mLocalAdapter.getItems(); + int position = -1; + int size = friends.size(); + for (int i = 0; i < size; i++) { + ContactsCacheManager.Friend friend = friends.get(i); + if (friend.firstChar.startsWith(str)) { + position = i; + break; + } + } + + if (position >= 0) { + RecyclerView.LayoutManager layoutManager = mRecyclerFriends.getLayoutManager(); + if (layoutManager instanceof LinearLayoutManager) { + ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, 0); + } else { + mRecyclerFriends.smoothScrollToPosition(position); + } + + mTvIndexShow.setText(str); + mTvIndexShow.setVisibility(View.VISIBLE); + } + } + + @Override + public boolean onQueryTextSubmit(String query) { + return false; + } + + // 搜索文字改变时 + @SuppressLint("SetTextI18n") + @Override + public boolean onQueryTextChange(String newText) { + if (TextUtils.isEmpty(newText)) { + transLabelX(false); + refreshLocalView(); + + if (mRecyclerFriends.getAdapter() != mLocalAdapter) { + // 设置本地Trigger + adapterSelectedTrigger = mLocalAdapter; + mRecyclerFriends.setAdapter(mLocalAdapter); + } + // 关闭键盘 + TDevice.hideSoftKeyboard(mSearchView); + } else { + mIndex.setVisibility(View.GONE); + + mTvLabel.setText("@" + newText); + transLabelX(true); + + mSearchAdapter.onSearchTextChanged(newText); + + if (mRecyclerFriends.getAdapter() != mSearchAdapter) { + // 设置本地trigger为搜索适配器 + adapterSelectedTrigger = mSearchAdapter; + mRecyclerFriends.setAdapter(mSearchAdapter); + } + + // 有搜索时隐藏 + showError(EmptyLayout.HIDE_LAYOUT); + } + return true; + } + + private float labelHideTransX = -1; + private float labelBeforeTransX = -1; + + private void transLabelX(boolean show) { + if (labelHideTransX == -1) + labelHideTransX = TDevice.dipToPx(getResources(), 52); + + if (show) { + if (labelBeforeTransX == 0) + return; + else + labelBeforeTransX = 0; + } else { + if (labelBeforeTransX == labelHideTransX) + return; + else + labelBeforeTransX = labelHideTransX; + } + + if (mTvLabel.getTag() == null) { + mTvLabel.animate() + .setInterpolator(new AnticipateOvershootInterpolator(2.5f)) + .setDuration(260); + mTvLabel.setTag(mTvLabel.animate()); + } + + mTvLabel.animate() + .translationXBy(mTvLabel.getTranslationX()) + .translationX(labelBeforeTransX) + .start(); + } + + /** + * 结束时发送选中的文字 + *

    + * isLabel 表示是否从点击Label触发,如果是则发送Label的文字 + */ + private void sendSelectData(boolean isLabel) { + List friendNames = new ArrayList<>(); + if (isLabel) { + String queryLabel = mTvLabel.getText().toString().replace("@", ""); + if (!TextUtils.isEmpty(queryLabel)) { + friendNames.add(queryLabel); + } + } + + if (mSelectedFriends.size() > 0) { + for (Author author : mSelectedFriends) { + friendNames.add(author.getName()); + } + + // 回调前进行最近联系人存储 + ContactsCacheManager.addRecentCache(CollectionUtil.toArray(mSelectedFriends, Author.class)); + } + + // 回送@列表 + final String[] names = CollectionUtil.toArray(friendNames, String.class); + synchronized (UserSelectFriendsActivity.class) { + if (textParentLinkedHolder != null) { + RichEditText editText = textParentLinkedHolder.item; + if (editText != null) + editText.appendMention(names); + } + } + + Intent result = new Intent(); + result.putExtra("data", names); + setResult(RESULT_OK, result); + + finish(); + } + + private boolean checkNetIsAvailable() { + if (!TDevice.hasInternet()) { + AppContext.showToastShort(getString(R.string.tip_network_error)); + return false; + } + return true; + } + + private void showError(int type) { + EmptyLayout layout = mEmptyLayout; + if (layout != null) { + layout.setErrorType(type); + } + } + + + /** + * 刷新选中的布局 + */ + private void updateSelectView() { + mSelectContainer.removeAllViews(); + if (mSelectedFriends.size() == 0) + mHorizontalScrollView.setVisibility(View.GONE); + else { + mHorizontalScrollView.setVisibility(View.VISIBLE); + for (final Author author : mSelectedFriends) { + ImageView ivIcon = (ImageView) LayoutInflater.from(this) + .inflate(R.layout.activity_main_select_friend_label_container_item, mSelectContainer, false); + + ivIcon.setTag(R.id.iv_show_icon, author); + ivIcon.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Author tag = (Author) v.getTag(R.id.iv_show_icon); + onSelectIconClick(tag); + mSelectContainer.removeView(v); + } + }); + mSelectContainer.addView(ivIcon); + Glide.with(this).load(author.getPortrait()).error(R.mipmap.widget_default_face).into(ivIcon); + } + } + } + + private void onSelectIconClick(Author author) { + mSelectedFriends.remove(author); + // 通知适配器 + adapterSelectedTrigger.trigger(author, false); + // 通知最近联系人 + recentSelectedTrigger.trigger(author, false); + } + + private ContactsCacheManager.SelectedTrigger recentSelectedTrigger; + private ContactsCacheManager.SelectedTrigger adapterSelectedTrigger; + + /** + * 尝试插入一个选中,如果不允许则不插入,并返回false + */ + private boolean tryInsertSelected(Author author) { + boolean allow = mSelectedFriends.size() < 10; + if (allow) { + mSelectedFriends.add(author); + updateSelectView(); + } else + AppContext.showToastShort(getString(R.string.check_count_hint)); + return allow; + } + + /** + * 移除选中 + */ + private void removeSelected(Author author) { + int index = ContactsCacheManager.indexOfContacts(mSelectedFriends, author); + if (index >= 0) { + mSelectedFriends.remove(index); + updateSelectView(); + } + } + + /** + * 最近联系人触发 + */ + @Override + public void tryTriggerSelected(RecentContactsView.Model model, ContactsCacheManager.SelectedTrigger trigger) { + if (ContactsCacheManager.checkInContacts(mSelectedFriends, model.author)) { + removeSelected(model.author); + trigger.trigger(model, false); + // 通知适配器 + adapterSelectedTrigger.trigger(model.author, false); + } else { + if (tryInsertSelected(model.author)) { + trigger.trigger(model, true); + // 通知适配器 + adapterSelectedTrigger.trigger(model.author, true); + } + } + } + + /** + * 适配器触发 + */ + @Override + public void tryTriggerSelected(ContactsCacheManager.Friend friend, ContactsCacheManager.SelectedTrigger trigger) { + if (ContactsCacheManager.checkInContacts(mSelectedFriends, friend.author)) { + removeSelected(friend.author); + trigger.trigger(friend, false); + // 通知最近联系人 + recentSelectedTrigger.trigger(friend.author, false); + } else { + if (tryInsertSelected(friend.author)) { + trigger.trigger(friend, true); + // 通知最近联系人 + recentSelectedTrigger.trigger(friend.author, true); + } + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/activities/UserSendMessageActivity.java b/app/src/main/java/net/oschina/app/improve/user/activities/UserSendMessageActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..a272647854fb82bbc3687b4ffa021c9119d7e534 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/activities/UserSendMessageActivity.java @@ -0,0 +1,307 @@ +package net.oschina.app.improve.user.activities; + + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.support.design.widget.CoordinatorLayout; +import android.text.TextUtils; +import android.view.View; +import android.widget.EditText; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BaseRecyclerViewActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Message; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.behavior.KeyboardInputDelegation; +import net.oschina.app.improve.media.ImageGalleryActivity; +import net.oschina.app.improve.media.SelectImageActivity; +import net.oschina.app.improve.media.config.SelectOptions; +import net.oschina.app.improve.user.adapter.UserSendMessageAdapter; +import net.oschina.app.improve.user.helper.ContactsCacheManager; +import net.oschina.app.improve.utils.PicturesCompressor; +import net.oschina.app.improve.utils.QuickOptionDialogHelper; +import net.oschina.app.util.HTMLUtil; + +import java.io.File; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * 发送消息界面 + * Created by huanghaibin_dev + * on 2016/8/18. + */ +public class UserSendMessageActivity extends BaseRecyclerViewActivity + implements BaseRecyclerAdapter.OnItemLongClickListener { + + @Bind(R.id.root) + CoordinatorLayout mCoordinatorLayout; + private KeyboardInputDelegation mDelegation; + + EditText mViewInput; + private User mReceiver; + private ProgressDialog mDialog; + private boolean isFirstLoading = true; + private Map mSendQuent = new HashMap<>(); + + public static void show(Context context, User sender) { + Intent intent = new Intent(context, UserSendMessageActivity.class); + intent.putExtra("receiver", sender); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_user_send_message; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mAdapter.setOnItemLongClickListener(this); + } + + @Override + protected void initData() { + super.initData(); + mDialog = new ProgressDialog(this); + mReceiver = (User) getIntent().getSerializableExtra("receiver"); + setTitle(mReceiver.getName()); + init(); + } + + /** + * 下拉刷新为加载更多 + */ + @Override + public void onRefreshing() { + OSChinaApi.getMessageList(mReceiver.getId(), mBean.getNextPageToken(), mHandler); + } + + /** + * 去掉上拉加载 + */ + @Override + public void onLoadMore() { + + } + + protected void init() { + mDelegation = KeyboardInputDelegation.delegation(this, mCoordinatorLayout, null); + mDelegation.showEmoji(getSupportFragmentManager()); + mDelegation.showPic(new View.OnClickListener() { + @Override + public void onClick(View v) { + + SelectImageActivity.show(UserSendMessageActivity.this, new SelectOptions.Builder() + .setHasCam(true) + .setSelectCount(1) + .setCallback(new SelectOptions.Callback() { + @Override + public void doSelected(String[] images) { + final File file = new File(images[0]); + String path = file.getPath(); + if (mSendQuent.containsKey(getFileName(path))) { + Toast.makeText(UserSendMessageActivity.this, "图片已经在发送队列", Toast.LENGTH_SHORT).show(); + } else { + compress(path, new Run()); + } + } + }).build()); + } + }); + + mDelegation.setSendListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String content = mDelegation.getInputText().replaceAll("[ \\s\\n]+", " "); + if (TextUtils.isEmpty(content)) { + Toast.makeText(UserSendMessageActivity.this, "请输入文字", Toast.LENGTH_SHORT).show(); + return; + } + mDialog.setMessage("正在发送中..."); + mDialog.show(); + OSChinaApi.pubMessage(mReceiver.getId(), content, new CallBack(null)); + } + }); + mViewInput = mDelegation.getInputView(); + } + + @SuppressWarnings("StatementWithEmptyBody") + @Override + public void onItemClick(int position, long itemId) { + Message message = mAdapter.getItem(position); + if(message==null) + return; + if (Message.TYPE_IMAGE == message.getType()) { + if (message.getId() == 0) { + // TODO: 2017/10/27 + } else if (message.getId() == -1) { //重新发送 + message.setId(0); + mAdapter.updateItem(position); + File file = new File(message.getResource()); + OSChinaApi.pubMessage(mReceiver.getId(), file, new CallBack(getFileName(file.getPath()))); + } else { + ImageGalleryActivity.show(this, message.getResource(), true, true); + } + } + } + + @Override + protected void setListData(ResultBean> resultBean) { + super.setListData(resultBean); + if (isFirstLoading) { + scrollToBottom(); + isFirstLoading = false; + } + + } + + private void scrollToBottom() { + mRecyclerView.scrollToPosition(mAdapter.getItems().size() - 1); + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + return new UserSendMessageAdapter(this); + } + + @Override + public void onLongClick(int position, long itemId) { + final Message message = mAdapter.getItem(position); + if (message == null || TextUtils.isEmpty(message.getContent())) return; + QuickOptionDialogHelper.with(getContext()) + .addCopy(HTMLUtil.delHTMLTag(message.getContent())) + .show(); + } + + private class CallBack extends TextHttpResponseHandler { + private String filePath; + + private CallBack(String filePath) { + this.filePath = filePath; + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (filePath != null) { + Message message = mSendQuent.get(filePath); + message.setId(-1); + mAdapter.updateItem(mAdapter.getItems().indexOf(message)); + } + Toast.makeText(UserSendMessageActivity.this, "发送失败,请检查数据", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + Type type = new TypeToken>() { + }.getType(); + try { + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + if (filePath != null) { + Message message = mSendQuent.get(filePath); + if (message != null) { + mAdapter.removeItem(message); + mSendQuent.remove(filePath); + delete(filePath); + } + } + mAdapter.addItem(resultBean.getResult()); + scrollToBottom(); + mViewInput.setText(""); + } else { + if (filePath != null) { + Message message = mSendQuent.get(filePath); + message.setId(-1); + mAdapter.updateItem(mAdapter.getItems().indexOf(message)); + } + Toast.makeText(UserSendMessageActivity.this, resultBean.getMessage(), Toast.LENGTH_SHORT).show(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onFinish() { + super.onFinish(); + mDialog.dismiss(); + } + + @Override + public void onStart() { + super.onStart(); + // 发送前添加到最近联系人队列 + ContactsCacheManager.addRecentCache(mReceiver); + } + } + + private void compress(final String oriPath, final Run runnable) { + final String path = getFilesDir() + "/message/" + getFileName(oriPath); + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + if (PicturesCompressor.compressImage(oriPath, path, 512 * 1024)) { + runnable.setPath(path); + runOnUiThread(runnable); + } + } + }); + } + + private String getFileName(String filePath) { + return filePath.substring(filePath.lastIndexOf("/") + 1); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private void delete(String path) { + File file = new File(path); + if (file.exists()) + file.delete(); + } + + private class Run implements Runnable { + private String path; + + @Override + public void run() { + File file = new File(path); + OSChinaApi.pubMessage(mReceiver.getId(), file, new CallBack(getFileName(path))); + Message message = new Message(); + message.setType(Message.TYPE_IMAGE); + message.setSender(AccountHelper.getUser()); + message.setResource(path); + mSendQuent.put(getFileName(path), message); + mAdapter.addItem(message); + scrollToBottom(); + } + + public void setPath(String path) { + this.path = path; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/activities/UserTweetActivity.java b/app/src/main/java/net/oschina/app/improve/user/activities/UserTweetActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..892bb69ee3d437592f3a9634b5c671ccea21b0b5 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/activities/UserTweetActivity.java @@ -0,0 +1,69 @@ +package net.oschina.app.improve.user.activities; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.Fragment; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.tweet.fragments.TweetFragment; + +/** + * Created by fei on 2016/8/18. + * desc: + */ + +public class UserTweetActivity extends BackActivity { + + private long uid; + private Fragment userTweetFragment; + + /** + * show activity + * + * @param context context + * @param uid uid + */ + public static void show(Context context, long uid) { + Intent intent = new Intent(context, UserTweetActivity.class); + intent.putExtra("uid", uid); + context.startActivity(intent); + } + + @Override + protected boolean initBundle(Bundle bundle) { + uid = bundle.getLong("uid", 0); + return uid > 0 || super.initBundle(bundle); + } + + @Override + protected int getContentView() { + return R.layout.activity_main_user_tweet; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + if (userTweetFragment == null) + userTweetFragment = TweetFragment.instantiate(uid, 0,false); + getSupportFragmentManager().beginTransaction().add(R.id.user_tweet_container, userTweetFragment).commit(); + } + + + @Override + public void onBackPressed() { + super.onBackPressed(); + if (userTweetFragment != null) { + getSupportFragmentManager().beginTransaction().attach(userTweetFragment).commit(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + finish(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/adapter/CollectionAdapter.java b/app/src/main/java/net/oschina/app/improve/user/adapter/CollectionAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..3ee500fbd3938bddb5fedabbc4a1c67760a09dd4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/adapter/CollectionAdapter.java @@ -0,0 +1,80 @@ +package net.oschina.app.improve.user.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Collection; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.User; +import net.oschina.app.util.StringUtils; + +/** + * Created by haibin + * on 2016/10/18. + */ + +public class CollectionAdapter extends BaseRecyclerAdapter { + public CollectionAdapter(Context context) { + super(context, ONLY_FOOTER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new CollectionViewHolder(mInflater.inflate(R.layout.item_list_collection, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Collection item, int position) { + CollectionViewHolder h = (CollectionViewHolder) holder; + String type; + switch (item.getType()) { + case News.TYPE_SOFTWARE: + type = "软件"; + break; + case News.TYPE_QUESTION: + type = "问答"; + break; + case News.TYPE_BLOG: + type = "博客"; + break; + case News.TYPE_TRANSLATE: + type = "翻译"; + break; + case News.TYPE_EVENT: + type = "活动"; + break; + case News.TYPE_NEWS: + type = "资讯"; + break; + default: + type = "链接"; + break; + } + h.mTypeView.setText(type); + h.mTitleView.setText(item.getTitle()); + h.mFavDateText.setText(StringUtils.formatSomeAgo(item.getFavDate())); + User user = item.getAuthorUser(); + h.mAuthorText.setText(user != null ? user.getName() : "匿名"); + h.mCommentCountText.setText(String.valueOf(item.getCommentCount())); + h.mFavCountText.setText(String.valueOf(item.getFavCount())); + } + + private static class CollectionViewHolder extends RecyclerView.ViewHolder { + private TextView mTypeView, mTitleView, mCommentCountText, mFavCountText, mAuthorText, mFavDateText; + + CollectionViewHolder(View itemView) { + super(itemView); + mTypeView = (TextView) itemView.findViewById(R.id.tv_type); + mTitleView = (TextView) itemView.findViewById(R.id.tv_title); + mCommentCountText = (TextView) itemView.findViewById(R.id.tv_comment_count); + mFavCountText = (TextView) itemView.findViewById(R.id.tv_fav_count); + mAuthorText = (TextView) itemView.findViewById(R.id.tv_user); + mFavDateText = (TextView) itemView.findViewById(R.id.tv_fav_date); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/adapter/UserActiveAdapter.java b/app/src/main/java/net/oschina/app/improve/user/adapter/UserActiveAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..b833f6a82a7accd559a186f6c09e8b10199f0b71 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/adapter/UserActiveAdapter.java @@ -0,0 +1,141 @@ +package net.oschina.app.improve.user.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Active; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.Origin; +import net.oschina.app.improve.utils.parser.TweetParser; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * Created by thanatos + * on 16/7/14. + */ +public class UserActiveAdapter extends BaseRecyclerAdapter { + + /** + * @param context Context + */ + public UserActiveAdapter(Context context) { + super(context, ONLY_FOOTER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.list_item_active, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder vh, Active item, int position) { + ViewHolder holder = (ViewHolder) vh; + + Author author = item.getAuthor(); + holder.mIdentityView.setup(author); + if (author == null) { + holder.mViewNick.setText("匿名用户"); + holder.mViewPortrait.setup(0, "匿名用户", ""); + } else { + holder.mViewPortrait.setup(author); + holder.mViewNick.setText(item.getAuthor().getName()); + } + + holder.mViewTime.setText(StringUtils.formatSomeAgo(item.getPubDate())); + + Spannable spannable = TweetParser.getInstance().parse(mContext, item.getContent()); + holder.mViewContent.setText(spannable); + + holder.mViewTitle.setText(getWhichTitle(item.getOrigin())); + + if (item.getOrigin().getType() == Origin.ORIGIN_TYPE_TWEETS) { + holder.mViewReply.setVisibility(View.VISIBLE); + holder.mViewReply.setText(item.getOrigin().getDesc()); + } else { + holder.mViewReply.setVisibility(View.GONE); + } + + } + + private CharSequence getWhichTitle(Origin origin) { + if (origin == null) return "更新了动态"; + String desc = "评论了%s%s:"; + String which; + String title = "“" + origin.getDesc() + "”"; + switch (origin.getType()) { + case Origin.ORIGIN_TYPE_LINK: + which = "新闻"; + break; + case Origin.ORIGIN_TYPE_SOFTWARE: + which = "软件推荐"; + break; + case Origin.ORIGIN_TYPE_DISCUSS: + which = "帖子"; + break; + case Origin.ORIGIN_TYPE_BLOG: + which = "博客"; + break; + case Origin.ORIGIN_TYPE_TRANSLATION: + which = "翻译文章"; + break; + case Origin.ORIGIN_TYPE_ACTIVE: + which = "活动"; + break; + case Origin.ORIGIN_TYPE_NEWS: + which = "资讯"; + break; + case Origin.ORIGIN_TYPE_TWEETS: + which = "动弹"; + title = ""; + break; + default: + which = "文章"; + title = ""; + } + desc = String.format(desc, which, title); + if (title.length() == 0) return desc; + SpannableStringBuilder builder = new SpannableStringBuilder(desc); + int start = which.length() + 4; + int end = start + title.length() - 2; + ForegroundColorSpan cs = new ForegroundColorSpan(mContext.getResources().getColor(R.color.day_colorPrimary)); + builder.setSpan(cs, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + return builder; + } + + static class ViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.tv_nick) + TextView mViewNick; + @Bind(R.id.tv_time) + TextView mViewTime; + @Bind(R.id.tv_title) + TextView mViewTitle; + @Bind(R.id.tv_reply) + TextView mViewReply; + @Bind(R.id.tv_content) + TextView mViewContent; + @Bind(R.id.iv_portrait) + PortraitView mViewPortrait; + @Bind(R.id.identityView) + IdentityView mIdentityView; + + public ViewHolder(View view) { + super(view); + ButterKnife.bind(this, view); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/adapter/UserBlogAdapter.java b/app/src/main/java/net/oschina/app/improve/user/adapter/UserBlogAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..5d8285e4f715ab9224d5351ae65237481a2c47c3 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/adapter/UserBlogAdapter.java @@ -0,0 +1,85 @@ +package net.oschina.app.improve.user.adapter; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.RecyclerView; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ImageSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Blog; +import net.oschina.app.util.StringUtils; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * Created by thanatos on 16/8/17. + */ +public class UserBlogAdapter extends BaseRecyclerAdapter { + + public UserBlogAdapter(Context context, int mode) { + super(context, mode); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.fragment_item_blog, parent, false)); + } + + @SuppressWarnings("all") + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder h, Blog item, int position) { + ViewHolder holder = (ViewHolder) h; + + SpannableStringBuilder builder = new SpannableStringBuilder(); + if (item.isOriginal()) { + builder.append("[icon] "); + Drawable originate = mContext.getResources().getDrawable(R.mipmap.ic_label_originate); + originate.setBounds(0, 0, originate.getIntrinsicWidth(), originate.getIntrinsicHeight()); + ImageSpan imageSpan = new ImageSpan(originate, ImageSpan.ALIGN_BOTTOM); + builder.setSpan(imageSpan, 0, 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + if (item.isRecommend()) { + int start = builder.length(); + builder.append("[icon] "); + Drawable originate = mContext.getResources().getDrawable(R.mipmap.ic_label_recommend); + originate.setBounds(0, 0, originate.getIntrinsicWidth(), originate.getIntrinsicHeight()); + ImageSpan imageSpan = new ImageSpan(originate, ImageSpan.ALIGN_BOTTOM); + builder.setSpan(imageSpan, start, start + 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + builder.append(item.getTitle()); + holder.mViewTitle.setText(builder); + + holder.mViewContent.setText(item.getBody()); + String nick = item.getAuthor(); + holder.mViewHistory.setText(nick.length() > 9 ? nick.substring(0, 9) : nick + " " + StringUtils.formatSomeAgo(item.getPubDate())); + holder.mViewInfoCmm.setText(String.valueOf(item.getCommentCount())); + holder.mViewInfoVisual.setText(String.valueOf(item.getViewCount())); + + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.tv_item_blog_title) + TextView mViewTitle; + @Bind(R.id.tv_item_blog_body) + TextView mViewContent; + @Bind(R.id.tv_item_blog_history) + TextView mViewHistory; + @Bind(R.id.tv_info_view) + TextView mViewInfoVisual; + @Bind(R.id.tv_info_comment) + TextView mViewInfoCmm; + + public ViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/adapter/UserFansOrFollowAdapter.java b/app/src/main/java/net/oschina/app/improve/user/adapter/UserFansOrFollowAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..dd2b6a5ba1f79c3ce4587fc4474620e8537fab8a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/adapter/UserFansOrFollowAdapter.java @@ -0,0 +1,95 @@ +package net.oschina.app.improve.user.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.user.bean.UserFansOrFollows; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * Created by fei on 2016/8/24. + * desc: + */ + +public class UserFansOrFollowAdapter extends BaseRecyclerAdapter { + public UserFansOrFollowAdapter(Context context, int mode) { + super(context, mode); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new UserFansViewHolder(mInflater.inflate(R.layout.activity_item_user_flow, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, UserFansOrFollows item, int position) { + if (item == null) return; + final UserFansViewHolder vh = (UserFansViewHolder) holder; + vh.identityView.setup(item); + vh.mCiIcon.setup(item); + vh.mTvName.setText(item.getName()); + switch (item.getGender()) { + case 0: + vh.mIvSex.setVisibility(View.GONE); + break; + case 1: + vh.mIvSex.setVisibility(View.VISIBLE); + vh.mIvSex.setImageResource(R.mipmap.userinfo_icon_male); + break; + case 2: + vh.mIvSex.setVisibility(View.VISIBLE); + vh.mIvSex.setImageResource(R.mipmap.userinfo_icon_female); + break; + default: + break; + } + vh.mTvDesc.setText(item.getDesc()); + UserFansOrFollows.More more = item.getMore(); + if (more == null) return; + vh.mTvCity.setText(more.getCity()); + vh.mTvExp.setText(more.getExpertise()); + + } + + @Override + public void setOnItemClickListener(OnItemClickListener onItemClickListener) { + super.setOnItemClickListener(onItemClickListener); + } + + /** + * + */ + class UserFansViewHolder extends RecyclerView.ViewHolder { + + @Bind(R.id.identityView) + IdentityView identityView; + @Bind(R.id.iv_user_flow_icon) + PortraitView mCiIcon; + @Bind(R.id.tv_user_flow_name) + TextView mTvName; + @Bind(R.id.iv_user_flow_sex) + ImageView mIvSex; + @Bind(R.id.tv_user_flow_city) + TextView mTvCity; + @Bind(R.id.tv_user_flow_desc) + TextView mTvDesc; + @Bind(R.id.tv_user_flow_expertise) + TextView mTvExp; + + + UserFansViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/adapter/UserFavoritesAdapter.java b/app/src/main/java/net/oschina/app/improve/user/adapter/UserFavoritesAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..4addfd83cf8e963d548fcc3de4989ab4050f7e00 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/adapter/UserFavoritesAdapter.java @@ -0,0 +1,49 @@ +package net.oschina.app.improve.user.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.user.bean.UserFavorites; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * Created by fei on 2016/8/30. + * desc: + */ + +public class UserFavoritesAdapter extends BaseRecyclerAdapter { + + public UserFavoritesAdapter(Context context, int mode) { + super(context, mode); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + View view = mInflater.inflate(R.layout.list_cell_favorite, parent, false); + return new ViewHolder(view); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, UserFavorites item, int position) { + ViewHolder vh = (ViewHolder) holder; + vh.tvTitle.setText(item.getTitle()); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + @Bind(R.id.tv_favorite_title) + TextView tvTitle; + + public ViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/adapter/UserMentionAdapter.java b/app/src/main/java/net/oschina/app/improve/user/adapter/UserMentionAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..7a7d3e5b38c964a533d7e537e6bb02dcd66e5124 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/adapter/UserMentionAdapter.java @@ -0,0 +1,109 @@ +package net.oschina.app.improve.user.adapter; + +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.bean.Mention; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.Origin; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.utils.parser.MentionParser; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.PlatfromUtil; +import net.oschina.app.util.StringUtils; +import net.oschina.app.widget.TweetTextView; + +/** + * Created by huanghaibin_dev + * on 2016/8/16. + */ + +public class UserMentionAdapter extends BaseGeneralRecyclerAdapter { + private OnUserFaceClickListener mListener; + + public UserMentionAdapter(Callback callback) { + super(callback, ONLY_FOOTER); + initListener(); + } + + private void initListener() { + mListener = new UserMentionAdapter.OnUserFaceClickListener() { + @SuppressWarnings("ConstantConditions") + @Override + public void onClick(View v, int position) { + Author author = getItem(position).getAuthor(); + if (author != null) + OtherUserHomeActivity.show(mCallBack.getContext(), author.getId()); + } + }; + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + MentionViewHolder holder = new UserMentionAdapter.MentionViewHolder(mInflater.inflate(R.layout.item_list_comment, parent, false)); + holder.iv_user_avatar.setTag(R.id.iv_face, holder); + return holder; + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Mention item, int position) { + MentionViewHolder viewHolder = (MentionViewHolder) holder; + Author author = item.getAuthor(); + viewHolder.identityView.setup(author); + if (author != null) { + viewHolder.iv_user_avatar.setup(author); + viewHolder.tv_user_name.setText(author.getName()); + } else { + viewHolder.iv_user_avatar.setup(0, "匿名用户", ""); + viewHolder.tv_user_name.setText("匿名用户"); + } + viewHolder.iv_user_avatar.setOnClickListener(mListener); + PlatfromUtil.setPlatFromString(viewHolder.tv_platform, item.getAppClient()); + viewHolder.tv_comment_count.setText(String.valueOf(item.getCommentCount())); + viewHolder.tv_time.setText(StringUtils.formatSomeAgo(item.getPubDate())); + viewHolder.tv_content.setText(MentionParser.getInstance().parse(mContext, item.getContent())); + Origin origin = item.getOrigin(); + if (origin != null && !TextUtils.isEmpty(origin.getDesc())) { + viewHolder.tv_origin.setVisibility(View.VISIBLE); + viewHolder.tv_origin.setText(MentionParser.getInstance().parse(mContext, origin.getDesc())); + } else { + viewHolder.tv_origin.setVisibility(View.GONE); + } + + } + + private static class MentionViewHolder extends RecyclerView.ViewHolder { + IdentityView identityView; + PortraitView iv_user_avatar; + TextView tv_user_name, tv_time, tv_platform, tv_comment_count; + TweetTextView tv_content, tv_origin; + + MentionViewHolder(View itemView) { + super(itemView); + identityView = (IdentityView) itemView.findViewById(R.id.identityView); + iv_user_avatar = (PortraitView) itemView.findViewById(R.id.iv_user_avatar); + tv_user_name = (TextView) itemView.findViewById(R.id.tv_user_name); + tv_time = (TextView) itemView.findViewById(R.id.tv_time); + tv_content = (TweetTextView) itemView.findViewById(R.id.tv_content); + tv_origin = (TweetTextView) itemView.findViewById(R.id.tv_origin); + tv_platform = (TextView) itemView.findViewById(R.id.tv_platform); + tv_comment_count = (TextView) itemView.findViewById(R.id.tv_comment_count); + } + } + + private abstract class OnUserFaceClickListener implements View.OnClickListener { + @Override + public void onClick(View v) { + MentionViewHolder holder = (MentionViewHolder) v.getTag(R.id.iv_face); + onClick(v, holder.getAdapterPosition()); + } + + public abstract void onClick(View v, int position); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/adapter/UserMessageAdapter.java b/app/src/main/java/net/oschina/app/improve/user/adapter/UserMessageAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..50c81918923aec982ed6abecb695b78439e7b3d7 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/adapter/UserMessageAdapter.java @@ -0,0 +1,92 @@ +package net.oschina.app.improve.user.adapter; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.bean.Message; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.utils.parser.StringParser; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; + +/** + * Created by huanghaibin_dev + * on 2016/8/16. + */ + +public class UserMessageAdapter extends BaseGeneralRecyclerAdapter { + private OnUserFaceClickListener mListener; + + public UserMessageAdapter(Callback callback) { + super(callback, ONLY_FOOTER); + initListener(); + } + + private void initListener() { + mListener = new OnUserFaceClickListener() { + @SuppressWarnings("ConstantConditions") + @Override + public void onClick(View v, int position) { + User author = getItem(position).getSender(); + if (author != null) + OtherUserHomeActivity.show(mCallBack.getContext(), author.getId()); + } + }; + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + MessageViewHolder holder = new MessageViewHolder(mInflater.inflate(R.layout.item_list_message, parent, false)); + holder.iv_user_avatar.setTag(R.id.iv_face, holder); + return holder; + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Message item, int position) { + MessageViewHolder messageViewHolder = (MessageViewHolder) holder; + User author = item.getSender(); + messageViewHolder.iv_user_identify.setup(author); + if (author != null) { + messageViewHolder.iv_user_avatar.setup(author); + messageViewHolder.tv_user_name.setText(author.getName()); + } else { + messageViewHolder.iv_user_avatar.setup(0, "匿名用户", ""); + messageViewHolder.tv_user_name.setText("匿名用户"); + } + messageViewHolder.iv_user_avatar.setOnClickListener(mListener); + messageViewHolder.tv_content.setText(StringParser.getInstance().parse(mContext, item.getContent())); + messageViewHolder.tv_time.setText(StringUtils.formatSomeAgo(item.getPubDate())); + } + + private static class MessageViewHolder extends RecyclerView.ViewHolder { + PortraitView iv_user_avatar; + IdentityView iv_user_identify; + TextView tv_user_name, tv_time; + TextView tv_content; + + MessageViewHolder(View itemView) { + super(itemView); + iv_user_avatar = (PortraitView) itemView.findViewById(R.id.iv_user_avatar); + iv_user_identify = (IdentityView) itemView.findViewById(R.id.identityView); + tv_user_name = (TextView) itemView.findViewById(R.id.tv_user_name); + tv_time = (TextView) itemView.findViewById(R.id.tv_time); + tv_content = (TextView) itemView.findViewById(R.id.tv_content); + } + } + + private abstract class OnUserFaceClickListener implements View.OnClickListener { + @Override + public void onClick(View v) { + MessageViewHolder holder = (MessageViewHolder) v.getTag(R.id.iv_face); + onClick(v, holder.getAdapterPosition()); + } + + public abstract void onClick(View v, int position); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/adapter/UserQuestionAdapter.java b/app/src/main/java/net/oschina/app/improve/user/adapter/UserQuestionAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..d29d7a035712fd868b8b8b22625fddee90529e12 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/adapter/UserQuestionAdapter.java @@ -0,0 +1,66 @@ +package net.oschina.app.improve.user.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.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Question; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * Created by thanatos on 16/8/17. + */ +public class UserQuestionAdapter extends BaseRecyclerAdapter { + + public UserQuestionAdapter(Context context) { + super(context, ONLY_FOOTER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.fragment_item_question, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder h, Question item, int position) { + ViewHolder holder = (ViewHolder) h; + holder.mViewPortrait.setup(item.getAuthorId(), item.getAuthor(), item.getAuthorPortrait()); + holder.mViewTitle.setText(item.getTitle()); + holder.mViewContent.setText(item.getBody()); + String nick = item.getAuthor(); + holder.mViewHistory.setText(nick.length() > 9 + ? nick.substring(0, 9) + : nick + " " + StringUtils.formatSomeAgo(item.getPubDate())); + holder.mViewInfoCmm.setText(String.valueOf(item.getCommentCount())); + holder.mViewInfoVisual.setText(String.valueOf(item.getViewCount())); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.iv_ques_item_icon) + PortraitView mViewPortrait; + @Bind(R.id.tv_ques_item_title) + TextView mViewTitle; + @Bind(R.id.tv_ques_item_content) + TextView mViewContent; + @Bind(R.id.tv_ques_item_history) + TextView mViewHistory; + @Bind(R.id.tv_info_view) + TextView mViewInfoVisual; + @Bind(R.id.tv_info_comment) + TextView mViewInfoCmm; + + public ViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/adapter/UserSearchFriendsAdapter.java b/app/src/main/java/net/oschina/app/improve/user/adapter/UserSearchFriendsAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..6486ce5584edf552f41888305f9b4ba38146af9c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/adapter/UserSearchFriendsAdapter.java @@ -0,0 +1,411 @@ +package net.oschina.app.improve.user.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.user.helper.ContactsCacheManager; +import net.oschina.app.improve.utils.parser.RichTextParser; +import net.oschina.app.util.ImageLoader; +import net.oschina.app.util.TDevice; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.ButterKnife; +import cz.msebera.android.httpclient.Header; +import de.hdodenhof.circleimageview.CircleImageView; + + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ +public class UserSearchFriendsAdapter extends RecyclerView.Adapter + implements ContactsCacheManager.SelectedTrigger { + private static final int TYPE_TITLE = 0x1111; + private static final int TYPE_NONE = 0x0000; + private static final int TYPE_FOOTER = 0x0001; + + private final ContactsCacheManager.OnSelectedChangeListener listener; + private String mSearchContent; + private Context mContext; + + public UserSearchFriendsAdapter(Context context, ContactsCacheManager.OnSelectedChangeListener listener, + List selectPointer, List localFriendPointer) { + this.mContext = context; + this.listener = listener; + this.mSelectFriendsPointer = selectPointer; + this.mLocalFriendPointer = localFriendPointer; + } + + @Override + public int getItemViewType(int position) { + ContactsCacheManager.Friend friend = getItem(position); + if (friend.author == null) { + if (TextUtils.isEmpty(friend.firstChar)) { + return TYPE_FOOTER; + } else { + return TYPE_TITLE; + } + } else + return TYPE_NONE; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + switch (viewType) { + case TYPE_TITLE: + return new TitleViewHolder(inflater.inflate(R.layout.activity_item_select_friend_label, parent, false)); + case TYPE_FOOTER: + return new SearchViewHolder(inflater.inflate(R.layout.activity_item_search_friend_bottom, parent, false)); + default: + ViewHolder viewHolder = new ViewHolder(inflater.inflate(R.layout.activity_item_select_friend, parent, false)); + viewHolder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (v.getTag() != null && v.getTag() instanceof ContactsCacheManager.Friend) { + onViewHolderClick((ContactsCacheManager.Friend) v.getTag()); + } + } + }); + return viewHolder; + } + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + ContactsCacheManager.Friend friend = getItem(position); + if (holder instanceof TitleViewHolder) { + TitleViewHolder titleViewHolder = (TitleViewHolder) holder; + titleViewHolder.onBindView(friend); + } else if (holder instanceof SearchViewHolder) { + + } else { + ViewHolder viewHolder = (ViewHolder) holder; + viewHolder.onBindView(friend); + } + } + + private ContactsCacheManager.Friend getItem(int pos) { + if (pos < mSearchFriends.size()) { + return mSearchFriends.get(pos); + } else { + pos = pos - (mSearchFriends.size()); + return mNetFriends.get(pos); + } + } + + @Override + public int getItemCount() { + return mSearchFriends.size() + mNetFriends.size(); + } + + private void onViewHolderClick(ContactsCacheManager.Friend friend) { + if (listener != null) + listener.tryTriggerSelected(friend, this); + } + + private final List mLocalFriendPointer; + private final List mSelectFriendsPointer; + + public void onSearchTextChanged(String searchContent) { + this.mSearchContent = searchContent; + query(searchContent); + } + + private String getSearchContent() { + return mSearchContent; + } + + private final List mSearchFriends = new ArrayList<>(); + private final List mNetFriends = new ArrayList<>(); + + /** + * 查询文字 + */ + private void query(String queryText) { + mSearchFriends.clear(); + mNetFriends.clear(); + + if (TextUtils.isEmpty(queryText)) + return; + + ContactsCacheManager.Friend friend = new ContactsCacheManager.Friend(null, + mContext.getText(R.string.local_search_label).toString()); + mSearchFriends.add(friend); + + // R.string.search_net_label + mNetFriends.add(new ContactsCacheManager.Friend(null, null)); + + for (ContactsCacheManager.Friend mCacheFriend : mLocalFriendPointer) { + if (mCacheFriend.author == null) + continue; + Author author = mCacheFriend.author; + String name = author.getName(); + if (TextUtils.isEmpty(name)) continue; + + boolean isZH = RichTextParser.checkIsZH(queryText); + + boolean isMatch; + if (isZH) { + isMatch = name.contains(queryText); + } else { + String pg = mCacheFriend.pinyin; + String pinyin = RichTextParser.convertToPinyin(queryText, ContactsCacheManager.SPLIT_HEAD); + isMatch = pg.startsWith(pinyin) || pg.contains(pinyin); + } + + if (isMatch) { + mSearchFriends.add(mCacheFriend); + } + } + notifyDataSetChanged(); + } + + private int indexOfItem(ContactsCacheManager.Friend friend) { + int pos = mSearchFriends.indexOf(friend); + if (pos >= 0) + return pos; + else { + pos = mNetFriends.indexOf(friend); + if (pos >= 0) { + return pos + mSearchFriends.size(); + } + } + return -1; + } + + @Override + public void trigger(ContactsCacheManager.Friend friend, boolean selected) { + friend.isSelected = selected; + int pos = indexOfItem(friend); + if (pos >= 0) { + notifyItemChanged(pos); + } + } + + @Override + public void trigger(Author author, boolean selected) { + if (author == null || TextUtils.isEmpty(mSearchContent)) + return; + for (ContactsCacheManager.Friend friend : mSearchFriends) { + if (friend.author != null && + friend.author.getId() == author.getId()) { + trigger(friend, selected); + return; + } + } + + for (ContactsCacheManager.Friend friend : mNetFriends) { + if (friend.author != null && + friend.author.getId() == author.getId()) { + trigger(friend, selected); + return; + } + } + } + + private void addNetFriends(List friends) { + //R.string.net_search_label + int lastPos = mNetFriends.size() - 1; + ContactsCacheManager.Friend last = mNetFriends.get(lastPos); + mNetFriends.remove(last); + if (mNetFriends.size() == 0) { + ContactsCacheManager.Friend first = new ContactsCacheManager.Friend(null, + mContext.getText(R.string.net_search_label).toString()); + mNetFriends.add(first); + } + mNetFriends.addAll(friends); + mNetFriends.add(last); + notifyDataSetChanged(); + } + + // 判断是否是本地的或者已被选中的数据 + private boolean isLocalOrSelectedData(User user) { + for (ContactsCacheManager.Friend mCacheFriend : mLocalFriendPointer) { + if (mCacheFriend == null || mCacheFriend.author == null) + continue; + if (mCacheFriend.author.getId() == user.getId()) { + return true; + } + } + return ContactsCacheManager.checkInContacts(mSelectFriendsPointer, user); + } + + static class TitleViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.tv_index_label) + TextView mLabel; + + TitleViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + + void onBindView(ContactsCacheManager.Friend friend) { + if (TextUtils.isEmpty(friend.firstChar)) + itemView.setVisibility(View.GONE); + else { + mLabel.setText(friend.firstChar); + itemView.setVisibility(View.VISIBLE); + } + } + } + + static class ViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.iv_portrait) + CircleImageView mCirclePortrait; + @Bind(R.id.tv_name) + TextView mtvName; + @Bind(R.id.iv_select) + ImageView mViewSelect; + @Bind(R.id.line) + View mLine; + + ViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + + void onBindView(final ContactsCacheManager.Friend item) { + // Set Tag + itemView.setTag(item); + + if (item == null || item.author == null) { + itemView.setVisibility(View.GONE); + return; + } else + itemView.setVisibility(View.VISIBLE); + final Author author = item.author; + ImageLoader.loadImage(Glide.with(itemView.getContext()), mCirclePortrait, author.getPortrait(), R.mipmap.widget_default_face); + mCirclePortrait.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + OtherUserHomeActivity.show(v.getContext(), author.getId()); + } + }); + mtvName.setText(author.getName()); + + if (item.isSelected) { + mViewSelect.setVisibility(View.VISIBLE); + } else { + mViewSelect.setVisibility(View.GONE); + } + } + } + + class SearchViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + @Bind(R.id.pb_footer) + ProgressBar mProgressBar; + @Bind(R.id.tv_footer) + TextView mTvSearch; + private String mNextPageToken; + private String mOldSearchText; + + private SearchViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + mProgressBar.setVisibility(View.GONE); + itemView.setOnClickListener(this); + mTvSearch.setText(mTvSearch.getResources().getString(R.string.search_net_label)); + } + + @Override + public void onClick(final View v) { + requestData(v); + } + + private void requestData(final View v) { + String searchContent = getSearchContent(); + if (TextUtils.isEmpty(searchContent)) { + mNextPageToken = null; + AppContext.showToastShort(v.getResources().getString(R.string.search_null_hint)); + return; + } + + if (!TDevice.hasInternet()) { + AppContext.showToastShort(R.string.error_view_network_error_click_to_refresh); + return; + } + + // 关键词改变时清理Token + if (!searchContent.equals(mOldSearchText)) { + mNextPageToken = null; + mOldSearchText = searchContent; + } + + OSChinaApi.search(News.TYPE_FIND_PERSON, searchContent, + mNextPageToken == null ? null : mNextPageToken, + new TextHttpResponseHandler() { + @Override + public void onStart() { + super.onStart(); + mProgressBar.setVisibility(View.VISIBLE); + mTvSearch.setText(mTvSearch.getResources().getString(R.string.footer_type_loading)); + } + + @Override + public void onFinish() { + super.onFinish(); + mProgressBar.setVisibility(View.GONE); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mTvSearch.setText(mTvSearch.getResources().getString(R.string.search_error_hint)); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + Type type = new TypeToken>>() { + }.getType(); + + ResultBean> resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + PageBean pageBean = resultBean.getResult(); + mNextPageToken = pageBean.getNextPageToken(); + mTvSearch.setText(mTvSearch.getResources().getString(R.string.search_load_more_hint)); + + List users = pageBean.getItems(); + List authors = new ArrayList<>(); + + for (User user : users) { + if (user == null || user.getId() <= 0 + || isLocalOrSelectedData(user)) + continue; + authors.add(user); + } + + addNetFriends(ContactsCacheManager.sortToFriendModel(authors)); + } else { + mTvSearch.setText(mTvSearch.getResources().getString(R.string.state_not_more)); + } + } + }); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/adapter/UserSelectFriendsAdapter.java b/app/src/main/java/net/oschina/app/improve/user/adapter/UserSelectFriendsAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..45bd103d937a43dc276cf35c3e3e3a27fb10c597 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/adapter/UserSelectFriendsAdapter.java @@ -0,0 +1,206 @@ +package net.oschina.app.improve.user.adapter; + +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +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 net.oschina.app.R; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.user.helper.ContactsCacheManager; +import net.oschina.app.util.ImageLoader; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.ButterKnife; +import de.hdodenhof.circleimageview.CircleImageView; + + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ +public class UserSelectFriendsAdapter extends RecyclerView.Adapter implements ContactsCacheManager.SelectedTrigger { + // 第一个头部 + private static final int TYPE_FIRST = 0x1111; + private static final int TYPE_NONE = 0x0000; + private static final int TYPE_TOP = 0x0001; + private static final int TYPE_BOTTOM = 0x0010; + + private final ArrayList mItems = new ArrayList<>(); + private final ContactsCacheManager.OnSelectedChangeListener listener; + private final View mFirstView; + + public UserSelectFriendsAdapter(View firstView, ContactsCacheManager.OnSelectedChangeListener listener) { + mFirstView = firstView; + this.listener = listener; + } + + + @Override + public int getItemViewType(int position) { + if (position == 0) + return TYPE_FIRST; + + ContactsCacheManager.Friend item = mItems.get(position); + + int type = TYPE_NONE; + // 判断是否显示标题 + if (position == 1 || + ((position > 1) && (!mItems.get(position - 1).firstChar.equals(item.firstChar)))) { + type = type | TYPE_TOP; + } + + // 判断是否是类型结束,类型结束不显示底线 + int maxPos = getItemCount() - 1; + if ((position == maxPos) + || !(mItems.get(position + 1).firstChar.equals(item.firstChar))) { + // 如果是最后一个或者后面一个不是当前首字母的类型则当前item是当前类型最后一个 + type = type | TYPE_BOTTOM; + } + return type; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == TYPE_FIRST) { + return new RecyclerView.ViewHolder(mFirstView) { + }; + } + + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + ViewHolder userInfoViewHolder = new ViewHolder(inflater.inflate(R.layout.activity_item_select_friend, parent, false)); + userInfoViewHolder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (v.getTag() != null && v.getTag() instanceof ContactsCacheManager.Friend) { + onViewHolderClick((ContactsCacheManager.Friend) v.getTag()); + } + } + }); + + boolean showLabel = ((viewType & TYPE_TOP) == TYPE_TOP); + boolean showLine = !((viewType & TYPE_BOTTOM) == TYPE_BOTTOM); + userInfoViewHolder.initView(showLabel, showLine); + + return userInfoViewHolder; + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + // 第一位不做处理,预留为顶部最近联系人 + if (position == 0) + return; + + ContactsCacheManager.Friend item = mItems.get(position); + if (holder instanceof ViewHolder) { + ((ViewHolder) holder).onBindView(item); + } else { + holder.itemView.setVisibility(View.GONE); + } + } + + @Override + public int getItemCount() { + return this.mItems.size(); + } + + public void initItems(List items) { + mItems.clear(); + + // 添加一个空的UserFriend 用于最近联系人占位 + mItems.add(new ContactsCacheManager.Friend(null)); + if (items != null && items.size() > 0) + mItems.addAll(items); + + mItems.trimToSize(); + notifyDataSetChanged(); + } + + public List getItems() { + return mItems; + } + + private void onViewHolderClick(ContactsCacheManager.Friend friend) { + if (listener != null) + listener.tryTriggerSelected(friend, this); + } + + @Override + public void trigger(ContactsCacheManager.Friend friend, boolean selected) { + friend.isSelected = selected; + int pos = mItems.indexOf(friend); + if (pos >= 0) { + notifyItemChanged(pos); + } + } + + @Override + public void trigger(Author author, boolean selected) { + if (mItems.size() == 0 || author == null) + return; + for (ContactsCacheManager.Friend friend : mItems) { + if (friend.author != null && + friend.author.getId() == author.getId()) { + trigger(friend, selected); + return; + } + } + } + + static class ViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.tv_index_label) + TextView mLabel; + @Bind(R.id.iv_portrait) + CircleImageView mCirclePortrait; + @Bind(R.id.tv_name) + TextView mtvName; + @Bind(R.id.iv_select) + ImageView mViewSelect; + @Bind(R.id.line) + View mLine; + + ViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + + void onBindView(final ContactsCacheManager.Friend item) { + // 存储Tag + itemView.setTag(item); + + final Author author = item.author; + if (author == null || author.getId() <= 0 || TextUtils.isEmpty(author.getName())) { + itemView.setVisibility(View.GONE); + return; + } + + ImageLoader.loadImage(Glide.with(itemView.getContext()), + mCirclePortrait, author.getPortrait(), R.mipmap.widget_default_face); + mCirclePortrait.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + OtherUserHomeActivity.show(v.getContext(), author.getId()); + } + }); + mtvName.setText(author.getName()); + mLabel.setText(item.firstChar); + + mViewSelect.setVisibility(item.isSelected ? View.VISIBLE : View.GONE); + } + + void initView(boolean showLabel, boolean showLine) { + mLabel.setVisibility(showLabel ? View.VISIBLE : View.GONE); + mLine.setVisibility(showLine ? View.VISIBLE : View.GONE); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/adapter/UserSendMessageAdapter.java b/app/src/main/java/net/oschina/app/improve/user/adapter/UserSendMessageAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..43abbed3287f95318a87ee4e050cc237bb446ae1 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/adapter/UserSendMessageAdapter.java @@ -0,0 +1,285 @@ +package net.oschina.app.improve.user.adapter; + +import android.annotation.SuppressLint; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.bean.Message; +import net.oschina.app.improve.utils.parser.TweetParser; +import net.oschina.app.util.StringUtils; +import net.oschina.app.widget.TweetTextView; +import net.oschina.common.widget.Loading; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +/** + * Created by huanghaibin_dev + * on 2016/8/18. + */ +@SuppressLint("SimpleDateFormat") +public class UserSendMessageAdapter extends BaseGeneralRecyclerAdapter { + private static final int SENDER = 1; + private static final int SENDER_PICTURE = 2; + private static final int RECEIVER = 3; + private static final int RECEIVER_PICTURE = 4; + private long authorId; + + public UserSendMessageAdapter(Callback callback) { + super(callback, NEITHER); + authorId = AccountHelper.getUserId(); + } + + @SuppressWarnings("all") + @Override + public int getItemViewType(int position) { + Message item = getItem(position); + if (item.getSender().getId() == authorId) {//如果是个人发送的私信 + if (Message.TYPE_IMAGE == item.getType()) + return SENDER_PICTURE; + return SENDER; + } else { + if (Message.TYPE_IMAGE == item.getType()) + return RECEIVER_PICTURE; + return RECEIVER; + } + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + if (type == SENDER) + return new SenderViewHolder(mInflater.inflate(R.layout.item_list_user_send_message, parent, false)); + else if (type == SENDER_PICTURE) + return new SenderPictureViewHolder(mInflater.inflate(R.layout.item_list_user_send_message_picture, parent, false)); + else if (type == RECEIVER) + return new ReceiverViewHolder(mInflater.inflate(R.layout.item_list_receiver_message, parent, false)); + else + return new ReceiverPictureViewHolder(mInflater.inflate(R.layout.item_list_receiver_message_picture, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, final Message item, int position) { + Message preMessage = position != 0 ? getItem(position - 1) : null; + switch (getItemViewType(position)) { + case SENDER: + SenderViewHolder senderViewHolder = (SenderViewHolder) holder; + //parseAtUserContent(senderViewHolder.tv_sender, item.getContent()); + senderViewHolder.tv_sender.setText(TweetParser.getInstance().parse(mContext, item.getContent())); + formatTime(preMessage, item, senderViewHolder.tv_send_time); + break; + case SENDER_PICTURE: + final SenderPictureViewHolder senderPictureViewHolder = (SenderPictureViewHolder) holder; + if (item.getId() == 0) { + mCallBack.getImgLoader() + .load(item.getResource()) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .error(R.mipmap.ic_split_graph) + .into(senderPictureViewHolder.iv_sender_picture); + senderPictureViewHolder.loading.setVisibility(View.VISIBLE); + senderPictureViewHolder.loading.start(); + senderPictureViewHolder.iv_resend.setVisibility(View.INVISIBLE); + } else if (item.getId() == -1) { + mCallBack.getImgLoader() + .load(item.getResource()) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .error(R.mipmap.ic_split_graph) + .into(senderPictureViewHolder.iv_sender_picture); + senderPictureViewHolder.loading.setVisibility(View.GONE); + senderPictureViewHolder.loading.stop(); + senderPictureViewHolder.iv_resend.setVisibility(View.VISIBLE); + } else { + senderPictureViewHolder.loading.setVisibility(View.VISIBLE); + senderPictureViewHolder.loading.start(); + senderPictureViewHolder.iv_resend.setVisibility(View.INVISIBLE); + Glide.clear(senderPictureViewHolder.iv_sender_picture); + mCallBack.getImgLoader() + .load(AppOperator.getGlideUrlByUser(item.getResource())) + .listener(new RequestListener() { + @Override + public boolean onException(Exception e, GlideUrl model, Target target, boolean isFirstResource) { + senderPictureViewHolder.loading.setVisibility(View.GONE); + senderPictureViewHolder.loading.stop(); + return false; + } + + @Override + public boolean onResourceReady(GlideDrawable resource, GlideUrl model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { + senderPictureViewHolder.loading.setVisibility(View.GONE); + senderPictureViewHolder.loading.stop(); + return false; + } + }) + .placeholder(R.color.list_divider_color) + .error(R.mipmap.ic_split_graph) + .into(senderPictureViewHolder.iv_sender_picture); + + } + formatTime(preMessage, item, senderPictureViewHolder.tv_send_time); + break; + case RECEIVER: + ReceiverViewHolder receiverViewHolder = (ReceiverViewHolder) holder; + //parseAtUserContent(receiverViewHolder.tv_receiver, item.getContent()); + receiverViewHolder.tv_receiver.setText(TweetParser.getInstance().parse(mContext, item.getContent())); + formatTime(preMessage, item, receiverViewHolder.tv_send_time); + break; + case RECEIVER_PICTURE: + final ReceiverPictureViewHolder receiverPictureViewHolder = (ReceiverPictureViewHolder) holder; + receiverPictureViewHolder.loading.setVisibility(View.VISIBLE); + receiverPictureViewHolder.loading.start(); + mCallBack.getImgLoader() + .load(AppOperator.getGlideUrlByUser(item.getResource())) + .listener(new RequestListener() { + @Override + public boolean onException(Exception e, GlideUrl model, Target target, boolean isFirstResource) { + receiverPictureViewHolder.loading.setVisibility(View.GONE); + receiverPictureViewHolder.loading.stop(); + return false; + } + + @Override + public boolean onResourceReady(GlideDrawable resource, GlideUrl model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { + receiverPictureViewHolder.loading.setVisibility(View.GONE); + receiverPictureViewHolder.loading.stop(); + return false; + } + }) + .placeholder(R.color.list_divider_color) + .error(R.mipmap.ic_split_graph) + .into(receiverPictureViewHolder.iv_receiver_picture); + formatTime(preMessage, item, receiverPictureViewHolder.tv_send_time); + break; + } + } + + + private void formatTime(Message preMessage, Message item, TextView tv_time) { + tv_time.setVisibility(View.GONE); + if (preMessage == null) { + formatTime(tv_time, item.getPubDate()); + tv_time.setVisibility(View.VISIBLE); + } else { + if (checkTime(preMessage.getPubDate(), item.getPubDate())) { + formatTime(tv_time, item.getPubDate()); + tv_time.setVisibility(View.VISIBLE); + } + } + } + + private boolean checkTime(String firstTime, String secondTime) { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + long first = format.parse(firstTime).getTime(); + long second = format.parse(secondTime).getTime(); + return second - first > 300000; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + private String formatWeek(Date date) { + SimpleDateFormat format = new SimpleDateFormat("EEEE", Locale.CHINESE); + return format.format(date); + } + + private String formatDate(Date date) { + SimpleDateFormat format = new SimpleDateFormat("MM月dd日"); + return format.format(date); + } + + private void formatTime(TextView tv_time, String time) { + if (TextUtils.isEmpty(time)) return; + SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm"); + Date date = StringUtils.toDate(time); + tv_time.setText(formatWeek(date) + ", " + formatDate(date) + ", " + timeFormat.format(date)); + } + + /** + * 倒序 + * + * @param items items + */ + @Override + public void addAll(List items) { + if (items != null) { + mItems.addAll(0, items); + notifyDataSetChanged(); + } + + } + + private static class SenderViewHolder extends RecyclerView.ViewHolder { + TweetTextView tv_sender; + TextView tv_send_time; + + SenderViewHolder(View itemView) { + super(itemView); + tv_sender = (TweetTextView) itemView.findViewById(R.id.tv_sender); + tv_send_time = (TextView) itemView.findViewById(R.id.tv_send_time); + tv_sender.setMovementMethod(LinkMovementMethod.getInstance()); + tv_sender.setFocusable(false); + tv_sender.setDispatchToParent(true); + tv_sender.setLongClickable(false); + } + } + + private static class SenderPictureViewHolder extends RecyclerView.ViewHolder { + ImageView iv_sender_picture, iv_resend; + TextView tv_send_time; + Loading loading; + + SenderPictureViewHolder(View itemView) { + super(itemView); + iv_sender_picture = (ImageView) itemView.findViewById(R.id.iv_sender_picture); + iv_resend = (ImageView) itemView.findViewById(R.id.iv_resend); + tv_send_time = (TextView) itemView.findViewById(R.id.tv_send_time); + loading = (Loading) itemView.findViewById(R.id.loading); + } + } + + private static class ReceiverViewHolder extends RecyclerView.ViewHolder { + TweetTextView tv_receiver; + TextView tv_send_time; + + ReceiverViewHolder(View itemView) { + super(itemView); + tv_receiver = (TweetTextView) itemView.findViewById(R.id.tv_receiver); + tv_send_time = (TextView) itemView.findViewById(R.id.tv_send_time); + tv_receiver.setMovementMethod(LinkMovementMethod.getInstance()); + tv_receiver.setFocusable(false); + tv_receiver.setDispatchToParent(true); + tv_receiver.setLongClickable(false); + } + } + + private static class ReceiverPictureViewHolder extends RecyclerView.ViewHolder { + ImageView iv_receiver_picture; + TextView tv_send_time; + Loading loading; + + ReceiverPictureViewHolder(View itemView) { + super(itemView); + iv_receiver_picture = (ImageView) itemView.findViewById(R.id.iv_receiver_picture); + tv_send_time = (TextView) itemView.findViewById(R.id.tv_send_time); + loading = (Loading) itemView.findViewById(R.id.loading); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/adapter/UserTweetAdapter.java b/app/src/main/java/net/oschina/app/improve/user/adapter/UserTweetAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..cbcc142d84fea7bc36088000de48f005f1e2c7b3 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/adapter/UserTweetAdapter.java @@ -0,0 +1,356 @@ +package net.oschina.app.improve.user.adapter; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.support.v7.widget.RecyclerView; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.bean.simple.TweetLikeReverse; +import net.oschina.app.improve.tweet.activities.TweetPublishActivity; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.utils.Platform; +import net.oschina.app.improve.utils.parser.TweetParser; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.improve.widget.TweetPicturesLayout; +import net.oschina.app.util.ImageUtils; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; +import net.oschina.app.widget.TweetTextView; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import butterknife.ButterKnife; +import cz.msebera.android.httpclient.Header; + +/** + * Created by + * thanatos on 16/8/17. + */ +@SuppressWarnings("all") +public class UserTweetAdapter extends BaseGeneralRecyclerAdapter implements View.OnClickListener { + private Bitmap mRecordBitmap; + private View.OnClickListener mOnLikeClickListener; + private View.OnClickListener mOnDispatchClickListener; + private boolean isShowIdentityView; + + public UserTweetAdapter(Callback callback) { + super(callback, ONLY_FOOTER); + isShowIdentityView = true; + initListener(); + } + + public void setShowIdentityView(boolean showIdentityView) { + isShowIdentityView = showIdentityView; + } + + private void initListener() { + mOnLikeClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(mContext); + return; + } + final int position = Integer.valueOf(v.getTag().toString()); + Tweet tweet = getItem(position); + if (tweet == null) return; + OSChinaApi.reverseTweetLike(tweet.getId(), new TweetLikedHandler(position)); + } + }; + mOnDispatchClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!AccountHelper.isLogin()) { + UIHelper.showLoginActivity(mContext); + return; + } + final int position = Integer.valueOf(v.getTag().toString()); + Tweet tweet = getItem(position); + if (tweet == null) return; + String content = null; + About.Share share; + if (tweet.getAbout() == null) { + share = About.buildShare(tweet.getId(), OSChinaApi.CATALOG_TWEET); + share.title = tweet.getAuthor().getName(); + share.content = tweet.getContent(); + } else { + share = About.buildShare(tweet.getAbout()); + content = "//@" + tweet.getAuthor().getName() + " :" + tweet.getContent(); + content = TweetParser.getInstance().clearHtmlTag(content).toString(); + } + share.commitTweetId = tweet.getId(); + share.fromTweetId = tweet.getId(); + TweetPublishActivity.show(mContext, null, content, share); + } + }; + } + + private void initRecordImg(Context cxt) { + mRecordBitmap = BitmapFactory.decodeResource(cxt.getResources(), R.mipmap.audio3); + mRecordBitmap = ImageUtils.zoomBitmap(mRecordBitmap, + (int) TDevice.dipToPx(cxt.getResources(), 20f), (int) TDevice.dipToPx(cxt.getResources(), 20f)); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_list_tweet_improve, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder h, final Tweet item, int position) { + ViewHolder holder = (ViewHolder) h; + + final Author author = item.getAuthor(); + + if (author == null) { + holder.mViewPortrait.setup(0, "匿名用户", ""); + holder.mViewName.setText("匿名用户"); + } else { + holder.mViewPortrait.setup(author); + holder.mViewPortrait.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + OtherUserHomeActivity.show(mContext, author); + } + }); + + holder.mViewName.setText(author.getName()); + } + if (isShowIdentityView) { + holder.mIdentityView.setVisibility(View.VISIBLE); + holder.mIdentityView.setup(author); + } else { + holder.mIdentityView.setVisibility(View.GONE); + } + + + holder.mViewTime.setText(StringUtils.formatSomeAgo(item.getPubDate())); + holder.mViewPlatform.setText(String.format("来自 %s", Platform.getPlatform(item.getAppClient()))); + holder.mViewPlatform.setVisibility(TextUtils.isEmpty(Platform.getPlatform(item.getAppClient())) ? View.GONE : View.VISIBLE); + if (!TextUtils.isEmpty(item.getContent())) { + String content = item.getContent().replaceAll("[\n\\s]+", " "); + //holder.mViewContent.setText(AssimilateUtils.assimilate(mContext, content)); + holder.mViewContent.setText(TweetParser.getInstance().parse(mContext, content)); + holder.mViewContent.setMovementMethod(LinkMovementMethod.getInstance()); + holder.mViewContent.setFocusable(false); + holder.mViewContent.setDispatchToParent(true); + holder.mViewContent.setLongClickable(false); + } + + /* - @hide - */ + /*if (item.getAudio() != null) { + if (mRecordBitmap == null) { + initRecordImg(mContext); + } + ImageSpan recordImg = new ImageSpan(mContext, mRecordBitmap); + SpannableString str = new SpannableString("c"); + str.setSpan(recordImg, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + holder.mViewContent.setText(str); + holder.mViewContent.append(spannable); + } else { + holder.mViewContent.setText(spannable); + }*/ + + holder.mViewLikeState.setImageResource( + item.isLiked() + ? R.mipmap.ic_thumbup_actived + : R.mipmap.ic_thumb_normal); + holder.mLinearLike.setTag(position); + holder.mLinearLike.setOnClickListener(mOnLikeClickListener); + holder.mLinearDispatch.setTag(position); + holder.mLinearDispatch.setOnClickListener(mOnDispatchClickListener); + + Tweet.Image[] images = item.getImages(); + holder.mLayoutFlow.setImage(images); + + /* - statistics - */ + if (item.getStatistics() != null) { + holder.mViewLikeCount.setText(String.valueOf(item.getStatistics().getLike())); + holder.mViewCmmCount.setText(String.valueOf(item.getStatistics().getComment())); + int mDispatchCount = item.getStatistics().getTransmit(); + if (mDispatchCount <= 0) { + //holder.mViewDispatchCount.setVisibility(View.GONE); + holder.mViewDispatchCount.setText("转发"); + } else { + holder.mViewDispatchCount.setVisibility(View.VISIBLE); + holder.mViewDispatchCount.setText(String.valueOf(item.getStatistics().getTransmit())); + } + } else { + holder.mViewLikeCount.setText(String.valueOf(item.getLikeCount())); + holder.mViewCmmCount.setText(String.valueOf(item.getCommentCount())); + holder.mViewDispatchCount.setVisibility(View.GONE); + } + String textCount = holder.mViewLikeCount.getText().toString(); + holder.mViewLikeCount.setText("0".equals(textCount) ? "赞" : textCount); + + String textComCount = holder.mViewCmmCount.getText().toString(); + holder.mViewCmmCount.setText("0".equals(textComCount) ? "评论" : textComCount); + + /* - about - */ + if (item.getAbout() != null) { + holder.mLayoutRef.setVisibility(View.VISIBLE); + holder.mLayoutRef.setTag(position); + holder.mLayoutRef.setOnClickListener(this); + + About about = item.getAbout(); + holder.mLayoutRefImages.setImage(about.getImages()); + + if (!About.check(about)) { + holder.mViewRefTitle.setVisibility(View.VISIBLE); + holder.mViewRefTitle.setText("不存在或已删除的内容"); + holder.mViewRefContent.setText("抱歉,该内容不存在或已被删除"); + } else { + if (about.getType() == OSChinaApi.COMMENT_TWEET) { + holder.mViewRefTitle.setVisibility(View.GONE); + String aname = "@" + about.getTitle(); + String cnt = about.getContent(); + //Spannable spannable = AssimilateUtils.assimilate(mContext, cnt); + Spannable spannable = TweetParser.getInstance().parse(mContext, cnt); + SpannableStringBuilder builder = new SpannableStringBuilder(); + builder.append(aname + ": "); + builder.append(spannable); + ForegroundColorSpan span = new ForegroundColorSpan( + mContext.getResources().getColor(R.color.day_colorPrimary)); + builder.setSpan(span, 0, aname.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + holder.mViewRefContent.setMaxLines(Integer.MAX_VALUE); + holder.mViewRefContent.setText(builder); + } else { + holder.mViewRefTitle.setVisibility(View.VISIBLE); + holder.mViewRefTitle.setText(about.getTitle()); + holder.mViewRefContent.setMaxLines(3); + holder.mViewRefContent.setEllipsize(TextUtils.TruncateAt.END); + holder.mViewRefContent.setText(about.getContent()); + } + } + } else { + holder.mLayoutRef.setVisibility(View.GONE); + } + } + + /** + * 点击引用时触发 + * + * @param v Ref View + */ + @Override + public void onClick(View v) { + int position = Integer.valueOf(v.getTag().toString()); + Tweet tweet = getItem(position); + if (tweet == null) return; + About about = tweet.getAbout(); + if (about == null) return; + UIHelper.showDetail(mContext, about.getType(), about.getId(), about.getHref()); + } + + /** + * Tweet Item View Holder + */ + public static class ViewHolder extends RecyclerView.ViewHolder { + @Bind(R.id.iv_tweet_face) + PortraitView mViewPortrait; + @Bind(R.id.identityView) + IdentityView mIdentityView; + @Bind(R.id.tv_tweet_name) + TextView mViewName; + @Bind(R.id.tv_tweet_time) + TextView mViewTime; + @Bind(R.id.tv_tweet_platform) + TextView mViewPlatform; + @Bind(R.id.tv_tweet_like_count) + TextView mViewLikeCount; + @Bind(R.id.tv_tweet_comment_count) + TextView mViewCmmCount; + @Bind(R.id.tweet_item) + TweetTextView mViewContent; + @Bind(R.id.iv_like_state) + ImageView mViewLikeState; + @Bind(R.id.fl_image) + TweetPicturesLayout mLayoutFlow; + @Bind(R.id.tv_ref_title) + TextView mViewRefTitle; + @Bind(R.id.tv_ref_content) + TextView mViewRefContent; + @Bind(R.id.layout_ref_images) + TweetPicturesLayout mLayoutRefImages; + @Bind(R.id.iv_dispatch) + ImageView mViewDispatch; + @Bind(R.id.tv_dispatch_count) + TextView mViewDispatchCount; + @Bind(R.id.layout_ref) + LinearLayout mLayoutRef; + @Bind(R.id.ll_like) + LinearLayout mLinearLike; + @Bind(R.id.ll_dispatch) + LinearLayout mLinearDispatch; + + + public ViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } + + /** + * 点赞请求的回调 + */ + private class TweetLikedHandler extends TextHttpResponseHandler { + private int position; + + TweetLikedHandler(int position) { + this.position = position; + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + SimplexToast.show(mContext, "点赞操作失败"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + Tweet tweet = getItem(position); + if (tweet == null) return; + tweet.setLiked(resultBean.getResult().isLiked()); + tweet.setLikeCount(resultBean.getResult().getLikeCount()); + if (tweet.getStatistics() != null) { + tweet.getStatistics().setLike(resultBean.getResult().getLikeCount()); + } + updateItem(position); + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/bean/UserFansOrFollows.java b/app/src/main/java/net/oschina/app/improve/user/bean/UserFansOrFollows.java new file mode 100644 index 0000000000000000000000000000000000000000..d02f84a92e7364f2ee93358a45ba85389e607e01 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/bean/UserFansOrFollows.java @@ -0,0 +1,93 @@ +package net.oschina.app.improve.user.bean; + +import net.oschina.app.improve.bean.simple.Author; + +import java.io.Serializable; + +/** + * Created by fei on 2016/8/24. + * desc: + */ + +public class UserFansOrFollows extends Author implements Serializable { + private String desc; + private More more; + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public More getMore() { + return more; + } + + public void setMore(More more) { + this.more = more; + } + + public static class More implements Serializable { + private String joinDate; + private String city; + private String expertise; + private String platform; + + public String getJoinDate() { + return joinDate; + } + + public void setJoinDate(String joinDate) { + this.joinDate = joinDate; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getExpertise() { + return expertise; + } + + public void setExpertise(String expertise) { + this.expertise = expertise; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + @Override + public String toString() { + return "More{" + + "joinDate='" + joinDate + '\'' + + ", city='" + city + '\'' + + ", expertise='" + expertise + '\'' + + ", platform='" + platform + '\'' + + '}'; + } + } + + @Override + public String toString() { + return "UserFansOrFollows{" + + "id=" + id + + ", name='" + name + '\'' + + ", portrait='" + portrait + '\'' + + ", gender=" + gender + + ", desc='" + desc + '\'' + + ", relation=" + relation + + ", more=" + more + + '}'; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/bean/UserFavorites.java b/app/src/main/java/net/oschina/app/improve/user/bean/UserFavorites.java new file mode 100644 index 0000000000000000000000000000000000000000..d75bf765fa6d897fa8e7f50371d5d16eb62fedec --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/bean/UserFavorites.java @@ -0,0 +1,58 @@ +package net.oschina.app.improve.user.bean; + +import java.io.Serializable; + +/** + * Created by fei on 2016/8/30. + * desc: + */ + +public class UserFavorites implements Serializable { + + private long id; + private int type; + private String title; + private String href; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + @Override + public String toString() { + return "UserFavorites{" + + "id=" + id + + ", type=" + type + + ", title='" + title + '\'' + + ", href='" + href + '\'' + + '}'; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/bean/UserFriend.java b/app/src/main/java/net/oschina/app/improve/user/bean/UserFriend.java new file mode 100644 index 0000000000000000000000000000000000000000..6aa246c7d7c69b77685927c7cb4dd7de7e081487 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/bean/UserFriend.java @@ -0,0 +1,85 @@ +package net.oschina.app.improve.user.bean; + +import android.support.annotation.NonNull; + +import net.oschina.app.improve.bean.simple.Author; + +import java.io.Serializable; + +/** + * Created by fei + * on 2016/12/23. + * desc: + */ + +public class UserFriend extends Author implements Serializable, Comparable { + private int showViewType; + private String showLabel = ""; + private transient boolean isGoneLine; + private transient boolean isSelected; + private transient int selectPosition; + + public int getShowViewType() { + return showViewType; + } + + public void setShowViewType(int showViewType) { + this.showViewType = showViewType; + } + + public String getShowLabel() { + return showLabel; + } + + public void setShowLabel(String showLabel) { + this.showLabel = showLabel; + } + + public boolean isGoneLine() { + return isGoneLine; + } + + public void setGoneLine(boolean goneLine) { + isGoneLine = goneLine; + } + + public boolean isSelected() { + return isSelected; + } + + public void setSelected(boolean selected) { + isSelected = selected; + } + + + public int getSelectPosition() { + return selectPosition; + } + + public void setSelectPosition(int selectPosition) { + this.selectPosition = selectPosition; + } + + @Override + public String toString() { + return "UserFriend{" + + "id=" + id + + ", portrait='" + portrait + '\'' + + ", name='" + name + '\'' + + ", showViewType=" + showViewType + + ", showLabel='" + showLabel + '\'' + + ", isGoneLine=" + isGoneLine + + ", isSelected=" + isSelected + + ", selectPosition=" + selectPosition + + '}'; + } + + @Override + public int compareTo(@NonNull UserFriend o) { + String showLabel = o.getShowLabel(); + String tempCompLabel = this.showLabel; + + return tempCompLabel.compareTo(showLabel); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/user/collection/UserCollectionActivity.java b/app/src/main/java/net/oschina/app/improve/user/collection/UserCollectionActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..e27cec2f385584f3bf5aa6a1fac662828de9b708 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/collection/UserCollectionActivity.java @@ -0,0 +1,34 @@ +package net.oschina.app.improve.user.collection; + +import android.content.Context; +import android.content.Intent; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; + +/** + * Created by haibin + * on 2016/12/30. + */ + +public class UserCollectionActivity extends BackActivity { + + public static void show(Context context) { + context.startActivity(new Intent(context, UserCollectionActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_user_collection; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + UserCollectionFragment fragment = UserCollectionFragment.newInstance(); + addFragment(R.id.fl_content, fragment); + new UserCollectionPresenter(fragment); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/collection/UserCollectionContract.java b/app/src/main/java/net/oschina/app/improve/user/collection/UserCollectionContract.java new file mode 100644 index 0000000000000000000000000000000000000000..addb09288fd47b07d996e2f1c748c0cadb8168b5 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/collection/UserCollectionContract.java @@ -0,0 +1,25 @@ +package net.oschina.app.improve.user.collection; + +import android.content.Context; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.bean.Collection; + +/** + * Created by haibin + * on 2016/12/30. + */ + + interface UserCollectionContract { + + interface View extends BaseListView { + void showGetFavSuccess(int position); + } + + interface Presenter extends BaseListPresenter { + void getCache(Context context); + + void getFavReverse(Collection collection, int position); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/collection/UserCollectionFragment.java b/app/src/main/java/net/oschina/app/improve/user/collection/UserCollectionFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..f70442075d61470c88aa304e52dfa06b75452651 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/collection/UserCollectionFragment.java @@ -0,0 +1,96 @@ +package net.oschina.app.improve.user.collection; + +import android.content.DialogInterface; + +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Collection; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.user.adapter.CollectionAdapter; +import net.oschina.app.improve.utils.CacheManager; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.util.UIHelper; + +import java.util.List; + +/** + * Created by haibin + * on 2016/12/30. + */ + +public class UserCollectionFragment extends BaseRecyclerFragment implements + UserCollectionContract.View, BaseRecyclerAdapter.OnItemLongClickListener { + + public static UserCollectionFragment newInstance() { + return new UserCollectionFragment(); + } + + @Override + protected void initData() { + if (mPresenter != null) + mPresenter.getCache(mContext); + super.initData(); + mAdapter.setOnItemLongClickListener(this); + } + + @Override + protected void onItemClick(Collection item, int position) { + switch (item.getType()) { + case News.TYPE_SOFTWARE: + SoftwareDetailActivity.show(mContext, item.getId(), true); + break; + case News.TYPE_QUESTION: + QuestionDetailActivity.show(mContext, item.getId(), true); + break; + case News.TYPE_BLOG: + BlogDetailActivity.show(mContext, item.getId(), true); + break; + case News.TYPE_TRANSLATE: + NewsDetailActivity.show(mContext, item.getId(), item.getType()); + break; + case News.TYPE_EVENT: + EventDetailActivity.show(mContext, item.getId(), true); + break; + case News.TYPE_NEWS: + NewsDetailActivity.show(mContext, item.getId(), true); + break; + default: + UIHelper.showUrlRedirect(mContext, item.getUrl()); + break; + } + } + + @Override + public void onLongClick(final int position, long itemId) { + final Collection collection = mAdapter.getItem(position); + if (collection == null) + return; + DialogHelper.getConfirmDialog(mContext, "删除收藏", "是否确认删除该内容吗?", "确认", "取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mPresenter.getFavReverse(collection, position); + } + }).show(); + } + + @Override + public void showGetFavSuccess(int position) { + mAdapter.removeItem(position); + } + + @Override + public void onRefreshSuccess(List data) { + super.onRefreshSuccess(data); + CacheManager.saveToJson(mContext, UserCollectionPresenter.CACHE_NAME, data); + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new CollectionAdapter(mContext); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/collection/UserCollectionPresenter.java b/app/src/main/java/net/oschina/app/improve/user/collection/UserCollectionPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..26ea65d131c701ab4db5783a556396441149708a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/collection/UserCollectionPresenter.java @@ -0,0 +1,155 @@ +package net.oschina.app.improve.user.collection; + +import android.content.Context; +import android.util.Log; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.Collection; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.utils.CacheManager; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2016/12/30. + */ + +class UserCollectionPresenter implements UserCollectionContract.Presenter { + private final UserCollectionContract.View mView; + private String mNextPageToken; + private Type mGsonType; + static final String CACHE_NAME = "user_collection_cache"; + + UserCollectionPresenter(UserCollectionContract.View mView) { + this.mView = mView; + mGsonType = new TypeToken>>() { + }.getType(); + this.mView.setPresenter(this); + } + + @Override + public void getCache(Context context) { + List items = CacheManager.readListJson(context, CACHE_NAME, Collection.class); + if (items == null || items.size() == 0) + return; + mView.onRefreshSuccess(items); + mView.onComplete(); + } + + @Override + public void onRefreshing() { + OSChinaApi.getCollectionList(null, new TextHttpResponseHandler() { + + @Override + public void onFinish() { + super.onFinish(); + mView.onComplete(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.tip_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> resultBean = new Gson().fromJson(responseString, mGsonType); + if (resultBean != null) { + List items; + if (resultBean.getCode() == 1) { + if(resultBean.getResult() == null){ + mView.showNotMore(); + }else { + mNextPageToken = resultBean.getResult().getNextPageToken(); + items = resultBean.getResult().getItems(); + mView.onRefreshSuccess(items); + if (items == null || items.size() < 20) + mView.showNotMore(); + } + } else { + mView.showNetworkError(R.string.tip_network_error); + } + } else { + mView.showNetworkError(R.string.tip_network_error); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.tip_network_error); + } + } + }); + } + + @Override + public void onLoadMore() { + OSChinaApi.getCollectionList(mNextPageToken, new TextHttpResponseHandler() { + @Override + public void onFinish() { + super.onFinish(); + mView.onComplete(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.tip_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Log.e("onSuccess", "" + responseString); + ResultBean> resultBean = new Gson().fromJson(responseString, mGsonType); + if (resultBean != null) { + List items; + if (resultBean.getCode() == 1) { + if(resultBean.getResult() == null){ + mView.showNotMore(); + }else { + mNextPageToken = resultBean.getResult().getNextPageToken(); + items = resultBean.getResult().getItems(); + mView.onLoadMoreSuccess(items); + if (items == null || items.size() < 20) + mView.showNotMore(); + } + } else { + mView.showNetworkError(R.string.tip_network_error); + } + } else { + mView.showNetworkError(R.string.tip_network_error); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.tip_network_error); + } + } + }); + } + + @Override + public void getFavReverse(final Collection collection, final int position) { + OSChinaApi.getFavReverse(collection.getId(), collection.getType(), new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.tip_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (collection.isFavorite()) { + mView.showGetFavSuccess(position); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/City.java b/app/src/main/java/net/oschina/app/improve/user/data/City.java new file mode 100644 index 0000000000000000000000000000000000000000..b6baee152a71ef87d5263553c042c506fd2842b7 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/City.java @@ -0,0 +1,58 @@ +package net.oschina.app.improve.user.data; + +import android.text.TextUtils; + +import net.oschina.app.improve.detail.db.Column; +import net.oschina.app.improve.detail.db.PrimaryKey; +import net.oschina.app.improve.detail.db.Table; + +import java.io.Serializable; + +/** + * 城市 + * Created by huanghaibin on 2017/8/21. + */ +@Table(tableName = "city") +public class City implements Serializable { + @PrimaryKey(column = "id",autoincrement = false) + private int id; + + @Column(column = "name") + private String name; + + @Column(column = "province") + private String province; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getProvince() { + return province; + } + + public void setProvince(String province) { + this.province = province; + } + + @Override + public boolean equals(Object obj) { + if (obj != null && obj instanceof City) { + String name = ((City) obj).getName(); + return !TextUtils.isEmpty(name) && name.equals(this.name); + } + return false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/CityAdapter.java b/app/src/main/java/net/oschina/app/improve/user/data/CityAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..985712ad825fb71302fe897fb33a39c7081af966 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/CityAdapter.java @@ -0,0 +1,52 @@ +package net.oschina.app.improve.user.data; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; + +/** + * 地区适配器 + * Created by huanghaibin on 2017/8/21. + */ + +class CityAdapter extends BaseRecyclerAdapter { + + + CityAdapter(Context context) { + super(context, NEITHER); + mSelectedPosition = 0; + } + + City getSelectedCity() { + if (mSelectedPosition < 0 || mSelectedPosition >= mItems.size()) + return null; + return mItems.get(mSelectedPosition); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new AreaHolder(mInflater.inflate(R.layout.item_list_city, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder + holder, City item, int position) { + AreaHolder h = (AreaHolder) holder; + h.mTextArea.setTextColor(position == mSelectedPosition ? 0xFF24cf5f : 0xff333333); + h.mTextArea.setText(item.getName()); + } + + private static class AreaHolder extends RecyclerView.ViewHolder { + TextView mTextArea; + + AreaHolder(View itemView) { + super(itemView); + mTextArea = (TextView) itemView.findViewById(R.id.tv_area); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/Field.java b/app/src/main/java/net/oschina/app/improve/user/data/Field.java new file mode 100644 index 0000000000000000000000000000000000000000..d76a94a78b8c849e5a0da13588bda65019cbf31b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/Field.java @@ -0,0 +1,53 @@ +package net.oschina.app.improve.user.data; + +import net.oschina.app.improve.detail.db.Column; +import net.oschina.app.improve.detail.db.PrimaryKey; +import net.oschina.app.improve.detail.db.Table; + +/** + * 专属领域 + * Created by huanghaibin on 2017/8/22. + */ + +@Table(tableName = "field") +public class Field { + @PrimaryKey(column = "id", autoincrement = false) + private int id; + @Column(column = "name") + private String name; + private boolean isSelected; + + + public boolean isSelected() { + return isSelected; + } + + public void setSelected(boolean selected) { + isSelected = selected; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object obj) { + if (obj != null && obj instanceof Field) { + int id = ((Field) obj).id; + return id == this.id; + } + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/user/data/FieldAdapter.java b/app/src/main/java/net/oschina/app/improve/user/data/FieldAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..ac52ee949b17907fe08d8c988562815773cab76d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/FieldAdapter.java @@ -0,0 +1,68 @@ +package net.oschina.app.improve.user.data; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; + +import java.util.ArrayList; +import java.util.List; + +/** + * 专属领域 + * Created by huanghaibin on 2017/8/22. + */ + +class FieldAdapter extends BaseRecyclerAdapter { + FieldAdapter(Context context) { + super(context, NEITHER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new FieldHolder(mInflater.inflate(R.layout.item_list_skill, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Field item, int position) { + FieldHolder h = (FieldHolder) holder; + h.mCheck.setChecked(item.isSelected()); + h.mTextSkill.setText(item.getName()); + } + + List getSelects() { + List fields = new ArrayList<>(); + for (Field field : mItems) { + if (field.isSelected()) + fields.add(field); + } + return fields; + } + + String getFields(List fields) { + if (fields.size() == 0) + return String.valueOf(-1); + StringBuilder sb = new StringBuilder(); + for (Field field : fields) { + sb.append(field.getId()); + sb.append(","); + } + return sb.substring(0, sb.length() - 1); + } + + private static class FieldHolder extends RecyclerView.ViewHolder { + CheckBox mCheck; + TextView mTextSkill; + + FieldHolder(View itemView) { + super(itemView); + mCheck = (CheckBox) itemView.findViewById(R.id.cb_selected); + mTextSkill = (TextView) itemView.findViewById(R.id.tv_skill); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/ModifyAreaActivity.java b/app/src/main/java/net/oschina/app/improve/user/data/ModifyAreaActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..834d59b57d425f8425d038f61cf57409a8386ba2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/ModifyAreaActivity.java @@ -0,0 +1,186 @@ +package net.oschina.app.improve.user.data; + +import android.app.Activity; +import android.content.Intent; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuItem; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.detail.db.DBManager; +import net.oschina.app.improve.widget.SimplexToast; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * 修改所在地区 + * Created by huanghaibin on 2017/8/21. + */ + +public class ModifyAreaActivity extends BackActivity { + + @Bind(R.id.rv_province) + RecyclerView mRecyclerProvince; + @Bind(R.id.rv_city) + RecyclerView mRecyclerCity; + private CityAdapter mAdapterCity; + private ProvinceAdapter mAdapterProvince; + static final int TYPE_MODIFY_AREA = 3; + + public static void show(Activity activity, User info) { + Intent intent = new Intent(activity, ModifyAreaActivity.class); + intent.putExtra("user_info", info); + activity.startActivityForResult(intent, TYPE_MODIFY_AREA); + } + + @Override + protected int getContentView() { + return R.layout.activity_modify_area; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + } + + @SuppressWarnings("ConstantConditions") + @Override + protected void initData() { + super.initData(); + + User mUser = (User) getIntent().getSerializableExtra("user_info"); + if (mUser == null || mUser.getMore() == null) { + finish(); + return; + } + mRecyclerProvince.setLayoutManager(new LinearLayoutManager(this)); + mRecyclerCity.setLayoutManager(new LinearLayoutManager(this)); + mAdapterCity = new CityAdapter(this); + mAdapterProvince = new ProvinceAdapter(this); + mRecyclerProvince.setAdapter(mAdapterProvince); + mRecyclerCity.setAdapter(mAdapterCity); + mAdapterProvince.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + mAdapterProvince.setSelectedPosition(position); + mAdapterCity.resetItem(DBManager.getCountryManager() + .get(City.class, String.format("where province='%s'", mAdapterProvince.getItem(position).getName()))); + mAdapterCity.setSelectedPosition(0); + } + }); + mAdapterCity.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + mAdapterCity.setSelectedPosition(position); + } + }); + mAdapterProvince.addAll(DBManager.getCountryManager().get(Province.class)); + String province = mUser.getMore().getProvince(); + if (TextUtils.isEmpty(province)) { + mAdapterCity.resetItem(DBManager.getCountryManager() + .get(City.class, String.format("where province='%s'", mAdapterProvince.getItem(0).getName()))); + } else { + Province p = new Province(); + p.setName(province); + int index = mAdapterProvince.getItems().indexOf(p); + if (index >= 0 && index < mAdapterProvince.getCount()) { + mAdapterProvince.setSelectedPosition(index); + mRecyclerProvince.scrollToPosition(index); + } + mAdapterCity.resetItem(DBManager.getCountryManager() + .get(City.class, String.format("where province='%s'", province))); + String city = mUser.getMore().getCity(); + if (TextUtils.isEmpty(city)) { + mAdapterCity.setSelectedPosition(0); + } else { + City c = new City(); + c.setName(city); + int i = mAdapterCity.getItems().indexOf(c); + if (i >= 0 && i < mAdapterCity.getCount()) { + mAdapterCity.setSelectedPosition(i); + mRecyclerCity.scrollToPosition(i); + } + } + } + + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_commit, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_commit) { + Province province = mAdapterProvince.getSelectedProvince(); + City city = mAdapterCity.getSelectedCity(); + if (province == null || city == null) { + SimplexToast.show(this, "请选择修改地址"); + return false; + } + modifyArea(province.getName(), city.getName()); + } + return false; + } + + private void modifyArea(String province, String city) { + showLoadingDialog("正在修改地址..."); + OSChinaApi.updateUserInfo(null, null, null, null, province, city, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (isDestroy()) { + return; + } + dismissLoadingDialog(); + SimplexToast.show(ModifyAreaActivity.this, "网络错误"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (isDestroy()) { + return; + } + dismissLoadingDialog(); + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean.isSuccess()) { + Intent intent = new Intent(); + intent.putExtra("user_info", bean.getResult()); + setResult(RESULT_OK, intent); + finish(); + }else { + SimplexToast.show(ModifyAreaActivity.this,bean.getMessage()); + } + + } catch (Exception e) { + e.printStackTrace(); + if (isDestroy()) { + return; + } + SimplexToast.show(ModifyAreaActivity.this, "修改失败"); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/ModifyDataActivity.java b/app/src/main/java/net/oschina/app/improve/user/data/ModifyDataActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..663ed16ebac341e48c22d1c457b829301d2f496a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/ModifyDataActivity.java @@ -0,0 +1,187 @@ +package net.oschina.app.improve.user.data; + +import android.app.Activity; +import android.content.Intent; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuItem; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.widget.BottomLineEditText; +import net.oschina.app.improve.widget.SimplexToast; + +import java.lang.reflect.Type; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * 修改界面 + * Created by huanghaibin on 2017/8/17. + */ + +public class ModifyDataActivity extends BackActivity { + + static final int TYPE_NICKNAME = 1; + static final int TYPE_SIGNATURE = 2; + private int mType; + + + @Bind(R.id.et_data) + BottomLineEditText mEditData; + + public static void show(Activity activity, User info, int type) { + Intent intent = new Intent(activity, ModifyDataActivity.class); + intent.putExtra("user_info", info); + intent.putExtra("type", type); + activity.startActivityForResult(intent, type); + } + + @Override + protected int getContentView() { + return R.layout.activity_modify_data; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + } + + @Override + protected void initData() { + super.initData(); + User mUser = (User) getIntent().getSerializableExtra("user_info"); + mType = getIntent().getIntExtra("type", 0); if (mUser == null || mType == 0) { + finish(); + return; + } + + if (mType == TYPE_NICKNAME) { + mEditData.setMaxCount(16); + mEditData.setSingleLine(); + mEditData.setText(mUser.getName()); + } else if (mType == TYPE_SIGNATURE) { + mEditData.setEllipsize(TextUtils.TruncateAt.END); + mEditData.setMaxCount(100); + mEditData.setText(mUser.getDesc()); + } + mEditData.setSelection(mEditData.getText().toString().length()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_commit, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_commit) { + String text = mEditData.getText().toString().trim(); + if (TextUtils.isEmpty(text)) { + return false; + } + if (mType == TYPE_NICKNAME) { + modifyNickname(text); + } else { + modifySignature(text); + } + } + return false; + } + + private void modifyNickname(String text) { + showLoadingDialog("正在修改昵称..."); + OSChinaApi.updateUserInfo(text, null, null, null, null, null, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (isDestroy()) { + return; + } + dismissLoadingDialog(); + SimplexToast.show(ModifyDataActivity.this, "网络错误"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (isDestroy()) { + return; + } + dismissLoadingDialog(); + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean.isSuccess()) { + Intent intent = new Intent(); + intent.putExtra("user_info", bean.getResult()); + setResult(RESULT_OK, intent); + finish(); + }else { + SimplexToast.show(ModifyDataActivity.this,bean.getMessage()); + } + + } catch (Exception e) { + e.printStackTrace(); + if (isDestroy()) { + return; + } + SimplexToast.show(ModifyDataActivity.this, "修改失败"); + } + } + }); + } + + private void modifySignature(String text) { + showLoadingDialog("正在修改签名..."); + OSChinaApi.updateUserInfo(null, text, null, null, null, null, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + + if (isDestroy()) { + return; + } + dismissLoadingDialog(); + SimplexToast.show(ModifyDataActivity.this, "网络错误"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (isDestroy()) { + return; + } + dismissLoadingDialog(); + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean.isSuccess()) { + Intent intent = new Intent(); + intent.putExtra("user_info", bean.getResult()); + setResult(RESULT_OK, intent); + finish(); + } + + } catch (Exception e) { + e.printStackTrace(); + if (isDestroy()) { + return; + } + SimplexToast.show(ModifyDataActivity.this, "修改失败"); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/ModifyFieldActivity.java b/app/src/main/java/net/oschina/app/improve/user/data/ModifyFieldActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..666954226fd5da61a8b343a16de2e8e5ee8e5a3e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/ModifyFieldActivity.java @@ -0,0 +1,163 @@ +package net.oschina.app.improve.user.data; + +import android.app.Activity; +import android.content.Intent; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.Menu; +import android.view.MenuItem; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.detail.db.DBManager; +import net.oschina.app.improve.widget.SimplexToast; + +import java.lang.reflect.Type; +import java.util.List; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * 修改专属领域界面 + * Created by huanghaibin on 2017/8/22. + */ + +public class ModifyFieldActivity extends BackActivity { + + static final int TYPE_FIELD = 5; + + @Bind(R.id.recyclerView) + RecyclerView mRecyclerField; + private FieldAdapter mAdapter; + + public static void show(Activity activity, User info) { + Intent intent = new Intent(activity, ModifyFieldActivity.class); + intent.putExtra("user_info", info); + activity.startActivityForResult(intent, TYPE_FIELD); + } + + @Override + protected int getContentView() { + return R.layout.activity_modify_skill; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mRecyclerField.setLayoutManager(new GridLayoutManager(this, 2)); + mAdapter = new FieldAdapter(this); + mAdapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + Field field = mAdapter.getItem(position); + if (field == null) return; + if (!field.isSelected()) { + List fields = mAdapter.getSelects(); + if (fields.size() >= 3) { + SimplexToast.show(ModifyFieldActivity.this, "最多选择3个"); + return; + } + } + field.setSelected(!field.isSelected()); + mAdapter.updateItem(position); + } + }); + mRecyclerField.setAdapter(mAdapter); + } + + @Override + protected void initData() { + super.initData(); + User mUser = (User) getIntent().getSerializableExtra("user_info"); + if (mUser == null || mUser.getMore() == null) { + finish(); + return; + } + mAdapter.resetItem(DBManager.getCountryManager().get(Field.class)); + int[] fields = mUser.getMore().getField(); + if (fields == null || fields.length == 0) + return; + try { + for (int id : fields) { + Field field = new Field(); + field.setId(id); + Field item = mAdapter.getItem(mAdapter.getItems().indexOf(field)); + assert item != null; + item.setSelected(true); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_commit, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_commit) { + modifyFields(mAdapter.getFields(mAdapter.getSelects())); + } + return false; + } + + + private void modifyFields(String fields) { + showLoadingDialog("正在修改..."); + OSChinaApi.updateUserInfo(null, null, null, fields, null, null, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (isDestroy()) { + return; + } + dismissLoadingDialog(); + SimplexToast.show(ModifyFieldActivity.this, "网络错误"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (isDestroy()) { + return; + } + dismissLoadingDialog(); + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean.isSuccess()) { + Intent intent = new Intent(); + intent.putExtra("user_info", bean.getResult()); + setResult(RESULT_OK, intent); + finish(); + }else { + SimplexToast.show(ModifyFieldActivity.this,bean.getMessage()); + } + + } catch (Exception e) { + e.printStackTrace(); + if (isDestroy()) { + return; + } + SimplexToast.show(ModifyFieldActivity.this, "修改失败"); + } + } + }); + } + + +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/ModifySkillActivity.java b/app/src/main/java/net/oschina/app/improve/user/data/ModifySkillActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..6105b00650e22e5584b1c5e994005fa547c0acc6 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/ModifySkillActivity.java @@ -0,0 +1,162 @@ +package net.oschina.app.improve.user.data; + +import android.app.Activity; +import android.content.Intent; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.Menu; +import android.view.MenuItem; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.detail.db.DBManager; +import net.oschina.app.improve.widget.SimplexToast; + +import java.lang.reflect.Type; +import java.util.List; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * 修改开发平台技能界面 + * Created by huanghaibin on 2017/8/22. + */ + +public class ModifySkillActivity extends BackActivity { + + static final int TYPE_SKILL = 4; + + @Bind(R.id.recyclerView) + RecyclerView mRecyclerSkill; + private SkillAdapter mAdapter; + + public static void show(Activity activity, User info) { + Intent intent = new Intent(activity, ModifySkillActivity.class); + intent.putExtra("user_info", info); + activity.startActivityForResult(intent, TYPE_SKILL); + } + + @Override + protected int getContentView() { + return R.layout.activity_modify_skill; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mRecyclerSkill.setLayoutManager(new GridLayoutManager(this, 2)); + mAdapter = new SkillAdapter(this); + mAdapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + Skill skill = mAdapter.getItem(position); + if (skill == null) return; + if (!skill.isSelected()) { + List skills = mAdapter.getSelects(); + if (skills.size() >= 5) { + SimplexToast.show(ModifySkillActivity.this, "最多选择5个"); + return; + } + } + skill.setSelected(!skill.isSelected()); + mAdapter.updateItem(position); + } + }); + mRecyclerSkill.setAdapter(mAdapter); + } + + @Override + protected void initData() { + super.initData(); + User mUser = (User) getIntent().getSerializableExtra("user_info"); + if (mUser == null || mUser.getMore() == null) { + finish(); + return; + } + mAdapter.resetItem(DBManager.getCountryManager().get(Skill.class)); + int[] skill = mUser.getMore().getSkill(); + if (skill == null || skill.length == 0) + return; + try { + for (int id : skill) { + Skill s = new Skill(); + s.setId(id); + Skill item = mAdapter.getItem(mAdapter.getItems().indexOf(s)); + assert item != null; + item.setSelected(true); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_commit, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_commit) { + modifySkill(mAdapter.getSkills(mAdapter.getSelects())); + } + return false; + } + + + private void modifySkill(String skill) { + showLoadingDialog("正在修改..."); + OSChinaApi.updateUserInfo(null, null, skill, null, null, null, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if (isDestroy()) { + return; + } + dismissLoadingDialog(); + SimplexToast.show(ModifySkillActivity.this, "网络错误"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + if (isDestroy()) { + return; + } + dismissLoadingDialog(); + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean.isSuccess()) { + Intent intent = new Intent(); + intent.putExtra("user_info", bean.getResult()); + setResult(RESULT_OK, intent); + finish(); + }else { + SimplexToast.show(ModifySkillActivity.this,bean.getMessage()); + } + + } catch (Exception e) { + e.printStackTrace(); + if (isDestroy()) { + return; + } + SimplexToast.show(ModifySkillActivity.this, "修改失败"); + } + } + }); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/MyDataActivity.java b/app/src/main/java/net/oschina/app/improve/user/data/MyDataActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..4dd5d3763c98bbfca4e0afcaed4f98c55d85f961 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/MyDataActivity.java @@ -0,0 +1,218 @@ +package net.oschina.app.improve.user.data; + +import android.content.Context; +import android.content.Intent; +import android.view.View; +import android.widget.TextView; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.media.SelectImageActivity; +import net.oschina.app.improve.media.config.SelectOptions; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.util.StringUtils; + +import java.io.File; +import java.lang.reflect.Type; + +import butterknife.Bind; +import butterknife.OnClick; +import cz.msebera.android.httpclient.Header; + +/** + * 我的资料界面 + 修改功能 + * Created by huanghaibin on 2017/8/22. + */ + +public class MyDataActivity extends BackActivity implements View.OnClickListener { + + @Bind(R.id.tv_nickname) + TextView mTextNickname; + @Bind(R.id.tv_join_date) + TextView mTextJoinTime; + @Bind(R.id.tv_area) + TextView mTextArea; + @Bind(R.id.tv_skill) + TextView mTextSkill; + @Bind(R.id.tv_field) + TextView mTextField; + @Bind(R.id.tv_signature) + TextView mTextSignature; + @Bind(R.id.iv_avatar) + PortraitView mImageAvatar; + private User mInfo; + + public static void show(Context context, User info) { + Intent intent = new Intent(context, MyDataActivity.class); + intent.putExtra("user_info", info); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_my_data; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + } + + @Override + protected void initData() { + super.initData(); + mInfo = (User) getIntent().getSerializableExtra("user_info"); + if (mInfo == null || mInfo.getMore() == null) { + finish(); + return; + } + mImageAvatar.setup(mInfo); + mImageAvatar.setVisibility(View.VISIBLE); + mImageAvatar.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + SelectImageActivity.show(MyDataActivity.this, new SelectOptions.Builder() + .setSelectCount(1) + .setHasCam(true) + .setCrop(700, 700) + .setCallback(new SelectOptions.Callback() { + @Override + public void doSelected(String[] images) { + String path = images[0]; + uploadNewPhoto(new File(path)); + } + }).build()); + } + }); + mTextNickname.setText(mInfo.getName()); + mTextJoinTime.setText(getText(StringUtils.formatYearMonthDayNew(mInfo.getMore().getJoinDate()))); + mTextArea.setText(mInfo.getMore().getCity()); + mTextField.setText(mInfo.getMore().getExpertise()); + mTextSkill.setText(mInfo.getMore().getPlatform()); + mTextSignature.setText(mInfo.getDesc()); + } + + @OnClick({R.id.ll_avatar, R.id.ll_nickname, R.id.ll_join_time, R.id.ll_area, + R.id.ll_skill, R.id.ll_field, R.id.ll_signature}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.ll_avatar: + SelectImageActivity.show(this, new SelectOptions.Builder() + .setSelectCount(1) + .setHasCam(true) + .setCrop(700, 700) + .setCallback(new SelectOptions.Callback() { + @Override + public void doSelected(String[] images) { + String path = images[0]; + uploadNewPhoto(new File(path)); + } + }).build()); + break; + case R.id.ll_nickname: + ModifyDataActivity.show(this, mInfo, ModifyDataActivity.TYPE_NICKNAME); + break; + case R.id.ll_area: + ModifyAreaActivity.show(this, mInfo); + break; + case R.id.ll_skill: + ModifySkillActivity.show(this, mInfo); + break; + case R.id.ll_field: + ModifyFieldActivity.show(this, mInfo); + break; + case R.id.ll_signature: + ModifyDataActivity.show(this, mInfo, ModifyDataActivity.TYPE_SIGNATURE); + break; + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode != RESULT_OK || data == null) + return; + mInfo = (User) data.getSerializableExtra("user_info"); + AccountHelper.updateUserCache(mInfo); + switch (requestCode) { + case ModifyDataActivity.TYPE_NICKNAME: + mTextNickname.setText(mInfo.getName()); + break; + case ModifyDataActivity.TYPE_SIGNATURE: + mTextSignature.setText(mInfo.getDesc()); + break; + case ModifyAreaActivity.TYPE_MODIFY_AREA: + mTextArea.setText(mInfo.getMore().getCity()); + break; + case ModifySkillActivity.TYPE_SKILL: + mTextSkill.setText(mInfo.getMore().getPlatform()); + break; + case ModifyFieldActivity.TYPE_FIELD: + mTextField.setText(mInfo.getMore().getExpertise()); + break; + } + } + + + private void uploadNewPhoto(File file) { + // 获取头像缩略图 + if (file == null || !file.exists() || file.length() == 0) { + AppContext.showToast(getString(R.string.title_icon_null)); + } else { + showLoadingDialog("正在上传头像"); + OSChinaApi.updateUserIcon(file, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + User userInfo = (User) resultBean.getResult(); + mImageAvatar.setup(userInfo); + mImageAvatar.setVisibility(View.VISIBLE); + //缓存用户信息 + AccountHelper.updateUserCache(userInfo); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + + @Override + public void onFinish() { + super.onFinish(); + if (isDestroy()) + return; + dismissLoadingDialog(); + } + }); + } + + } + + private String getText(String text) { + if (text == null || text.equalsIgnoreCase("null")) + return "<无>"; + else return text; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/Province.java b/app/src/main/java/net/oschina/app/improve/user/data/Province.java new file mode 100644 index 0000000000000000000000000000000000000000..7eeea00b2aa32323cccd6359d26bb24ec906ded9 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/Province.java @@ -0,0 +1,47 @@ +package net.oschina.app.improve.user.data; + +import android.text.TextUtils; + +import net.oschina.app.improve.detail.db.Column; +import net.oschina.app.improve.detail.db.PrimaryKey; +import net.oschina.app.improve.detail.db.Table; + +import java.io.Serializable; + +/** + * 省份 + * Created by huanghaibin on 2017/8/21. + */ + +@Table(tableName = "province") +public class Province implements Serializable { + @PrimaryKey(column = "id", autoincrement = false) + private int id; + @Column(column = "name") + private String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object obj) { + if (obj != null && obj instanceof Province) { + String name = ((Province) obj).getName(); + return !TextUtils.isEmpty(name) && name.equals(this.name); + } + return false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/ProvinceAdapter.java b/app/src/main/java/net/oschina/app/improve/user/data/ProvinceAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..e8b4afd5fa171131f1f99d80919ac9db4663886e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/ProvinceAdapter.java @@ -0,0 +1,58 @@ +package net.oschina.app.improve.user.data; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; + +/** + * 地区适配器 + * Created by huanghaibin on 2017/8/21. + */ + +class ProvinceAdapter extends BaseRecyclerAdapter { + + ProvinceAdapter(Context context) { + super(context, NEITHER); + mSelectedPosition = 0; + } + + + Province getSelectedProvince() { + if (mSelectedPosition < 0 || mSelectedPosition >= mItems.size()) + return null; + return mItems.get(mSelectedPosition); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new AreaHolder(mInflater.inflate(R.layout.item_list_province, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder + holder, Province item, int position) { + AreaHolder h = (AreaHolder) holder; + h.itemView.setBackgroundColor(position == mSelectedPosition ? 0xFFFFFFFF : 0xFFF6F6F6); + h.mTextArea.setText(item.getName()); + h.mViewSelected.setVisibility(position == mSelectedPosition ? View.VISIBLE : View.INVISIBLE); + h.mViewLine.setVisibility(position == mSelectedPosition ? View.INVISIBLE : View.VISIBLE); + } + + private static class AreaHolder extends RecyclerView.ViewHolder { + TextView mTextArea; + View mViewSelected; + View mViewLine; + + AreaHolder(View itemView) { + super(itemView); + mTextArea = (TextView) itemView.findViewById(R.id.tv_area); + mViewSelected = itemView.findViewById(R.id.viewSelected); + mViewLine = itemView.findViewById(R.id.line); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/Skill.java b/app/src/main/java/net/oschina/app/improve/user/data/Skill.java new file mode 100644 index 0000000000000000000000000000000000000000..21141adff228041e8e44506f6c2fd322400e8f15 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/Skill.java @@ -0,0 +1,52 @@ +package net.oschina.app.improve.user.data; + +import net.oschina.app.improve.detail.db.Column; +import net.oschina.app.improve.detail.db.PrimaryKey; +import net.oschina.app.improve.detail.db.Table; + +/** + * 开发平台、技能 + * Created by huanghaibin on 2017/8/22. + */ + +@Table(tableName = "skill") +public class Skill { + @PrimaryKey(column = "id", autoincrement = false) + private int id; + @Column(column = "name") + private String name; + private boolean isSelected; + + public boolean isSelected() { + return isSelected; + } + + public void setSelected(boolean selected) { + isSelected = selected; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object obj) { + if (obj != null && obj instanceof Skill) { + int id = ((Skill) obj).id; + return id == this.id; + } + return false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/SkillAdapter.java b/app/src/main/java/net/oschina/app/improve/user/data/SkillAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..057f04e4bebdfe3a3c3ee83a3a75a57505cb5480 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/SkillAdapter.java @@ -0,0 +1,68 @@ +package net.oschina.app.improve.user.data; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; + +import java.util.ArrayList; +import java.util.List; + +/** + * 开发平台、技能 + * Created by huanghaibin on 2017/8/22. + */ + +class SkillAdapter extends BaseRecyclerAdapter { + SkillAdapter(Context context) { + super(context, NEITHER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new SkillHolder(mInflater.inflate(R.layout.item_list_skill, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Skill item, int position) { + SkillHolder h = (SkillHolder) holder; + h.mCheck.setChecked(item.isSelected()); + h.mTextSkill.setText(item.getName()); + } + + private static class SkillHolder extends RecyclerView.ViewHolder { + CheckBox mCheck; + TextView mTextSkill; + + SkillHolder(View itemView) { + super(itemView); + mCheck = (CheckBox) itemView.findViewById(R.id.cb_selected); + mTextSkill = (TextView) itemView.findViewById(R.id.tv_skill); + } + } + + List getSelects() { + List skills = new ArrayList<>(); + for (Skill skill : mItems) { + if (skill.isSelected()) + skills.add(skill); + } + return skills; + } + + String getSkills(List skills) { + if (skills.size() == 0) + return String.valueOf(-1); + StringBuilder sb = new StringBuilder(); + for (Skill skill : skills) { + sb.append(skill.getId()); + sb.append(","); + } + return sb.substring(0, sb.length() - 1); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/UserDataActivity.java b/app/src/main/java/net/oschina/app/improve/user/data/UserDataActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..0ff9f72499213f2be838c0c88845295c4c887971 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/UserDataActivity.java @@ -0,0 +1,112 @@ +package net.oschina.app.improve.user.data; + +import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; +import android.view.View; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.widget.IdentityView; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.ui.empty.EmptyLayout; +import net.oschina.app.util.StringUtils; + +import butterknife.Bind; + +/** + * 用户资料:自己 + 其他人 + * Created by huanghaibin on 2017/8/14. + */ + +public class UserDataActivity extends BackActivity implements View.OnClickListener { + @Bind(R.id.iv_avatar) + PortraitView mUserFace; + + @Bind(R.id.identityView) + IdentityView identityView; + + @Bind(R.id.tv_name) + TextView mName; + + @Bind(R.id.tv_join_time) + TextView mJoinTime; + + @Bind(R.id.tv_location) + TextView mFrom; + + @Bind(R.id.tv_development_platform) + TextView mPlatFrom; + + @Bind(R.id.tv_academic_focus) + TextView mFocus; + + @Bind(R.id.tv_desc) + TextView mDesc; + + @Bind(R.id.error_layout) + EmptyLayout mErrorLayout; + + private User userInfo; + + public static void show(Context context, User info) { + Intent intent = new Intent(context, UserDataActivity.class); + intent.putExtra("user_info", info); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_user_data; + } + + @Override + protected void initWidget() { + super.initWidget(); +// setStatusBarDarkMode(); +// setDarkToolBar(); + } + + @Override + protected void initData() { + super.initData(); + userInfo = (User) getIntent().getSerializableExtra("user_info"); + if (userInfo == null) + return; + + if (userInfo.getId() != AccountHelper.getUserId()) { + String title = TextUtils.isEmpty(userInfo.getName()) ? "" : userInfo.getName(); + setTitle(title); + } + + fillUI(); + } + + @SuppressWarnings("deprecation") + private void fillUI() { + identityView.setup(userInfo); + mUserFace.setup(userInfo); + mUserFace.setOnClickListener(null); + mName.setText(getText(userInfo.getName())); + mJoinTime.setText(getText(StringUtils.formatYearMonthDayNew(userInfo.getMore().getJoinDate()))); + mFrom.setText(getText(userInfo.getMore().getCity())); + mPlatFrom.setText(getText(userInfo.getMore().getPlatform())); + mFocus.setText(getText(userInfo.getMore().getExpertise())); + mDesc.setText(getText(userInfo.getDesc())); + } + + + @Override + public void onClick(View v) { + + } + + private String getText(String text) { + if (text == null || text.equalsIgnoreCase("null")) + return "<无>"; + else return text; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/UserDataContract.java b/app/src/main/java/net/oschina/app/improve/user/data/UserDataContract.java new file mode 100644 index 0000000000000000000000000000000000000000..3296f80b95aa410764cae6a481d25484e67e08fc --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/UserDataContract.java @@ -0,0 +1,9 @@ +package net.oschina.app.improve.user.data; + +/** + * 用户资料+修改页面 + * Created by huanghaibin on 2017/8/17. + */ + +public interface UserDataContract { +} diff --git a/app/src/main/java/net/oschina/app/improve/user/data/UserDataPresenter.java b/app/src/main/java/net/oschina/app/improve/user/data/UserDataPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..7e9ac93e1ad908f82aa48d5f9826b6a0a31dc60c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/data/UserDataPresenter.java @@ -0,0 +1,9 @@ +package net.oschina.app.improve.user.data; + +/** + * 用户资料+修改页面 + * Created by huanghaibin on 2017/8/17. + */ + +class UserDataPresenter { +} diff --git a/app/src/main/java/net/oschina/app/improve/user/event/UserEventActivity.java b/app/src/main/java/net/oschina/app/improve/user/event/UserEventActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..28fb8a924263b0f77c49fd7a81db2f86b44166cb --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/event/UserEventActivity.java @@ -0,0 +1,71 @@ +package net.oschina.app.improve.user.event; + +import android.content.Context; +import android.content.Intent; +import android.text.TextUtils; +import android.view.View; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.ui.empty.EmptyLayout; + +import butterknife.Bind; + +/** + * Created by haibin + * on 2017/1/18. + */ + +public class UserEventActivity extends BackActivity implements UserEventContract.EmptyView { + @Bind(R.id.lay_error) + EmptyLayout mEmptyLayout; + + private UserEventPresenter mPresenter; + + public static void show(Context context, long authorId, String authorName) { + Intent intent = new Intent(context, UserEventActivity.class); + intent.putExtra("authorId", authorId); + if (!TextUtils.isEmpty(authorName)) + intent.putExtra("authorName", authorName); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_user_event; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mEmptyLayout.setOnLayoutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mEmptyLayout.getErrorState() != EmptyLayout.NETWORK_LOADING) { + mEmptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + mPresenter.onRefreshing(); + } + } + }); + + UserEventFragment fragment = UserEventFragment.newInstance(); + addFragment(R.id.lay_container, fragment); + Intent intent = getIntent(); + mPresenter = new UserEventPresenter(fragment, + this, + intent.getLongExtra("authorId", 0), + intent.getStringExtra("authorName")); + } + + @Override + public void hideEmptyLayout() { + mEmptyLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + } + + @Override + public void showErrorLayout(int errorType) { + mEmptyLayout.setErrorType(errorType); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/event/UserEventAdapter.java b/app/src/main/java/net/oschina/app/improve/user/event/UserEventAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..3c30f6b1ab1cf4e51ad5eae96ec3cb4bf8740e89 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/event/UserEventAdapter.java @@ -0,0 +1,145 @@ +package net.oschina.app.improve.user.event; + +import android.annotation.SuppressLint; +import android.content.res.Resources; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import net.oschina.app.OSCApplication; +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseGeneralRecyclerAdapter; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Event; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.TDevice; + +import java.util.List; +import java.util.Map; + +/** + * 用户活动列表 + * Created by haibin + * on 2016/10/27. + */ + +class UserEventAdapter extends BaseGeneralRecyclerAdapter implements BaseRecyclerAdapter.OnLoadingHeaderCallBack { + private OSCApplication.ReadState mReadState; + + UserEventAdapter(Callback callback) { + super(callback, ONLY_FOOTER); + mReadState = OSCApplication.getReadState("sub_list"); + setOnLoadingHeaderCallBack(this); + } + + @Override + public RecyclerView.ViewHolder onCreateHeaderHolder(ViewGroup parent) { + return new HeaderViewHolder(mHeaderView); + } + + @Override + public void onBindHeaderHolder(RecyclerView.ViewHolder holder, int position) { + + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new EventViewHolder(mInflater.inflate(R.layout.item_list_sub_event, parent, false)); + } + + @SuppressLint("SetTextI18n") + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, SubBean item, int position) { + EventViewHolder vh = (EventViewHolder) holder; + vh.tv_event_title.setText(item.getTitle()); + List images = item.getImages(); + if (images != null && images.size() > 0) { + mCallBack.getImgLoader() + .load(images.get(0).getHref()) + .placeholder(R.mipmap.event_cover_default) + .into(vh.iv_event); + } else { + vh.iv_event.setImageResource(R.mipmap.event_cover_default); + } + + Resources resources = mContext.getResources(); + + Map extras = item.getExtra(); + if (extras != null) { + vh.tv_event_pub_date.setText(StringUtils.getDateString(getExtraString(extras.get("eventStartDate")))); + + switch (getExtraInt(extras.get("eventStatus"))) { + case Event.STATUS_END: + setText(vh.tv_event_state, R.string.event_status_end, R.drawable.bg_event_end, 0x1a000000); + setTextColor(vh.tv_event_title, TDevice.getColor(resources, R.color.light_gray)); + break; + case Event.STATUS_ING: + setText(vh.tv_event_state, R.string.event_status_ing, R.drawable.bg_event_ing, 0xFF24cf5f); + break; + case Event.STATUS_SING_UP: + setText(vh.tv_event_state, R.string.event_status_sing_up, R.drawable.bg_event_end, 0x1a000000); + setTextColor(vh.tv_event_title, TDevice.getColor(resources, R.color.light_gray)); + break; + } + int typeStr = R.string.osc_site; + switch (getExtraInt(extras.get("eventType"))) { + case Event.EVENT_TYPE_OSC: + typeStr = R.string.event_type_osc; + break; + case Event.EVENT_TYPE_TEC: + typeStr = R.string.event_type_tec; + break; + case Event.EVENT_TYPE_OTHER: + typeStr = R.string.event_type_other; + break; + case Event.EVENT_TYPE_OUTSIDE: + typeStr = R.string.event_type_outside; + break; + } + vh.tv_event_type.setText(typeStr); + } + + vh.tv_event_title.setTextColor(TDevice.getColor(resources, + mReadState.already(item.getKey()) + ? R.color.text_desc_color : R.color.text_title_color)); + + } + + private void setText(TextView tv, int textRes, int bgRes, int textColor) { + tv.setText(textRes); + tv.setVisibility(View.VISIBLE); + tv.setBackgroundResource(bgRes); + tv.setTextColor(textColor); + } + + private void setTextColor(TextView tv, int textColor) { + tv.setTextColor(textColor); + tv.setVisibility(View.VISIBLE); + } + + private static class EventViewHolder extends RecyclerView.ViewHolder { + TextView tv_event_title, tv_description, tv_event_pub_date, tv_event_state, tv_event_type; + ImageView iv_event; + + EventViewHolder(View itemView) { + super(itemView); + tv_event_title = (TextView) itemView.findViewById(R.id.tv_event_title); + tv_event_state = (TextView) itemView.findViewById(R.id.tv_event_state); + tv_event_type = (TextView) itemView.findViewById(R.id.tv_event_type); + tv_description = (TextView) itemView.findViewById(R.id.tv_description); + tv_event_pub_date = (TextView) itemView.findViewById(R.id.tv_event_pub_date); + iv_event = (ImageView) itemView.findViewById(R.id.iv_event); + } + } + + private String getExtraString(Object object) { + return object == null ? "" : object.toString(); + } + + private int getExtraInt(Object object) { + return object == null ? 0 : Double.valueOf(object.toString()).intValue(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/event/UserEventContract.java b/app/src/main/java/net/oschina/app/improve/user/event/UserEventContract.java new file mode 100644 index 0000000000000000000000000000000000000000..4dc14bdb15cd4f3f01c9067324adc476b29ce5f4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/event/UserEventContract.java @@ -0,0 +1,27 @@ +package net.oschina.app.improve.user.event; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.bean.SubBean; + +/** + * Created by haibin + * on 2017/1/18. + */ + +interface UserEventContract { + + interface EmptyView { + void hideEmptyLayout(); + + void showErrorLayout(int errorType); + } + + interface View extends BaseListView { + + } + + interface Presenter extends BaseListPresenter { + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/event/UserEventFragment.java b/app/src/main/java/net/oschina/app/improve/user/event/UserEventFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..d499920fb0fd10b28b5c7a68da1524e7f6137711 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/event/UserEventFragment.java @@ -0,0 +1,29 @@ +package net.oschina.app.improve.user.event; + +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.detail.general.EventDetailActivity; + +/** + * Created by haibin + * on 2017/1/18. + */ + +public class UserEventFragment extends BaseRecyclerFragment + implements UserEventContract.View { + + public static UserEventFragment newInstance() { + return new UserEventFragment(); + } + + @Override + protected void onItemClick(SubBean subBean, int position) { + EventDetailActivity.show(mContext, subBean); + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new UserEventAdapter(this); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/event/UserEventPresenter.java b/app/src/main/java/net/oschina/app/improve/user/event/UserEventPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..df51032569996aeb41e59d43c00b458659edd438 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/event/UserEventPresenter.java @@ -0,0 +1,135 @@ +package net.oschina.app.improve.user.event; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.ui.empty.EmptyLayout; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2017/1/18. + */ + +class UserEventPresenter implements UserEventContract.Presenter { + private final UserEventContract.View mView; + private final UserEventContract.EmptyView mEmptyView; + private Type mGsonType; + private long mAuthorId; + private String mAuthorName; + private String mNextPageToken; + + UserEventPresenter(UserEventContract.View mView, + UserEventContract.EmptyView mEmptyView, + long mAuthorId, + String mAuthorName) { + this.mView = mView; + this.mEmptyView = mEmptyView; + this.mAuthorId = mAuthorId; + this.mAuthorName = mAuthorName; + mGsonType = new TypeToken>>() { + }.getType(); + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + OSChinaApi.getUserEvent(mAuthorId, mAuthorName, null, new TextHttpResponseHandler() { + @Override + public void onFinish() { + super.onFinish(); + mView.onComplete(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.tip_network_error); + if (!TextUtils.isEmpty(mNextPageToken)) + mEmptyView.showErrorLayout(EmptyLayout.NETWORK_ERROR); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> resultBean = new Gson().fromJson(responseString, mGsonType); + if (resultBean != null) { + List items; + if (resultBean.isSuccess()) { + mNextPageToken = resultBean.getResult().getNextPageToken(); + items = resultBean.getResult().getItems(); + mView.onRefreshSuccess(items); + if (items.size() < 20) + mView.showNotMore(); + mEmptyView.hideEmptyLayout(); + } else { + mView.showNetworkError(R.string.tip_network_error); + if (!TextUtils.isEmpty(mNextPageToken)) + mEmptyView.showErrorLayout(EmptyLayout.NETWORK_ERROR); + } + } else { + mView.showNetworkError(R.string.tip_network_error); + if (!TextUtils.isEmpty(mNextPageToken)) + mEmptyView.showErrorLayout(EmptyLayout.NETWORK_ERROR); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.tip_network_error); + if (!TextUtils.isEmpty(mNextPageToken)) + mEmptyView.showErrorLayout(EmptyLayout.NETWORK_ERROR); + } + } + }); + } + + @Override + public void onLoadMore() { + OSChinaApi.getUserEvent(mAuthorId, mAuthorName, mNextPageToken, new TextHttpResponseHandler() { + @Override + public void onFinish() { + super.onFinish(); + mView.onComplete(); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.tip_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> resultBean = new Gson().fromJson(responseString, mGsonType); + if (resultBean != null) { + List items; + if (resultBean.isSuccess()) { + mNextPageToken = resultBean.getResult().getNextPageToken(); + items = resultBean.getResult().getItems(); + mView.onLoadMoreSuccess(items); + if (items.size() < 20) + mView.showNotMore(); + } else { + mView.showNetworkError(R.string.tip_network_error); + } + } else { + mView.showNetworkError(R.string.tip_network_error); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showNetworkError(R.string.tip_network_error); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/fragments/UserActiveFragment.java b/app/src/main/java/net/oschina/app/improve/user/fragments/UserActiveFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..395e7fcab86033a3cbe86692f0261cda29eb4a74 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/fragments/UserActiveFragment.java @@ -0,0 +1,112 @@ +package net.oschina.app.improve.user.fragments; + +import android.os.Bundle; +import android.support.v4.app.Fragment; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseRecyclerViewFragment; +import net.oschina.app.improve.bean.Active; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.Origin; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.tweet.activities.TweetDetailActivity; +import net.oschina.app.improve.user.adapter.UserActiveAdapter; +import net.oschina.app.util.UIHelper; + +import java.lang.reflect.Type; + + +/** + * 某用户的动态(讨论)列表 + * Created by thanatos on 16/8/16. + */ +public class UserActiveFragment extends BaseRecyclerViewFragment { + + public static final String BUNDLE_KEY_USER_ID = "BUNDLE_KEY_USER_ID"; + + private long uid; + + public static Fragment instantiate(Long uid) { + Bundle bundle = new Bundle(); + bundle.putLong(BUNDLE_KEY_USER_ID, uid); + Fragment fragment = new UserActiveFragment(); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + uid = bundle.getLong(BUNDLE_KEY_USER_ID); + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + return new UserActiveAdapter(getContext()); + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } + + @Override + protected void requestData() { + String token = isRefreshing ? null : mBean.getNextPageToken(); + OSChinaApi.getUserActives(uid, token, mHandler); + } + + @Override + public void onItemClick(int position, long itemId) { + Origin origin = mAdapter.getItem(position).getOrigin(); + if (origin == null) + return; + switch (origin.getType()) { + case Origin.ORIGIN_TYPE_LINK: + UIHelper.showUrlRedirect(getContext(), origin.getHref()); + break; + case Origin.ORIGIN_TYPE_SOFTWARE: + SoftwareDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_DISCUSS: + QuestionDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_BLOG: + BlogDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_TRANSLATION: + NewsDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_ACTIVE: + EventDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_NEWS: + NewsDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_TWEETS: + TweetDetailActivity.show(getContext(), origin.getId()); + break; + default: + // pass + } + } + + @Override + protected boolean isNeedCache() { + return false; + } + + @Override + protected boolean isNeedEmptyView() { + return false; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/fragments/UserBlogFragment.java b/app/src/main/java/net/oschina/app/improve/user/fragments/UserBlogFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..56e0bd923ef7b106e30a0c3cbe99b38b1e2dd3cc --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/fragments/UserBlogFragment.java @@ -0,0 +1,141 @@ +package net.oschina.app.improve.user.fragments; + + +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.View; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseRecyclerViewFragment; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.main.subscription.BlogSubAdapter; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.SimplexToast; + +import java.lang.reflect.Type; + +import cz.msebera.android.httpclient.Header; + +/** + * created by thanatosx on 2016/8/16. + */ +public class UserBlogFragment extends BaseRecyclerViewFragment { + + @SuppressWarnings("unused") + public static final String HISTORY_BLOG = "history_my_blog"; + public static final String BUNDLE_KEY_USER_ID = "BUNDLE_KEY_USER_ID"; + private long userId; + + public static Fragment instantiate(long uid) { + Bundle bundle = new Bundle(); + bundle.putLong(BUNDLE_KEY_USER_ID, uid); + Fragment fragment = new UserBlogFragment(); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + userId = bundle.getLong(BUNDLE_KEY_USER_ID); + } + + @Override + protected void requestData() { + super.requestData(); + String token = isRefreshing ? null : mBean.getNextPageToken(); + OSChinaApi.getSomeoneBlogs(token, userId, null, mHandler); + } + + @Override + public void initData() { + super.initData(); + if(AccountHelper.isLogin() && AccountHelper.getUserId() == userId){ + mAdapter.setOnItemLongClickListener(new BaseRecyclerAdapter.OnItemLongClickListener() { + @Override + public void onLongClick(final int position, long itemId) { + final SubBean bean = mAdapter.getItem(position); + if (bean == null) return; + DialogHelper.getConfirmDialog(mContext, + "温馨提示", + "确定删除该博客?", + "删除", "取消", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + deleteBlog(bean.getId(), position); + } + }).show(); + } + }); + } + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + return new BlogSubAdapter(getContext(), BaseRecyclerAdapter.ONLY_FOOTER); + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } + + @Override + protected boolean isNeedCache() { + return false; + } + + @Override + protected boolean isNeedEmptyView() { + return false; + } + + @Override + public void onItemClick(int position, long itemId) { + SubBean blog = mAdapter.getItem(position); + if (blog == null) return; + BlogDetailActivity.show(getActivity(), blog.getId()); + } + + private void deleteBlog(long id, final int position) { + OSChinaApi.deleteBlog(id, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + if(mContext == null)return; + SimplexToast.show(mContext,"网络错误"); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean.getCode() == ResultBean.RESULT_SUCCESS) { + mAdapter.removeItem(position); + SimplexToast.show(mContext,"删除成功"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onFinish() { + super.onFinish(); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/user/fragments/UserCommentFragment.java b/app/src/main/java/net/oschina/app/improve/user/fragments/UserCommentFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..a35bba914002e2789fe88f5cb53ea2f0ea271495 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/fragments/UserCommentFragment.java @@ -0,0 +1,102 @@ +package net.oschina.app.improve.user.fragments; + +import android.content.Context; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseRecyclerViewFragment; +import net.oschina.app.improve.bean.Mention; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.Origin; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.tweet.activities.TweetDetailActivity; +import net.oschina.app.improve.user.activities.UserMessageActivity; +import net.oschina.app.improve.user.adapter.UserMentionAdapter; +import net.oschina.app.util.UIHelper; + +import java.lang.reflect.Type; + +/** + * Created by huanghaibin_dev + * on 2016/8/16. + */ + +public class UserCommentFragment extends BaseRecyclerViewFragment { + + + private UserMessageActivity activity; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context != null && context instanceof UserMessageActivity) { + activity = (UserMessageActivity) context; + } + } + + @Override + protected void onRequestSuccess(int code) { + super.onRequestSuccess(code); + if (activity != null && isRefreshing) activity.onRequestSuccess(1); + } + + @Override + protected void requestData() { + super.requestData(); + OSChinaApi.getMsgCommentList(isRefreshing ? null : mBean.getNextPageToken(), mHandler); + } + + @Override + public void onItemClick(int position, long itemId) { + Mention mention = mAdapter.getItem(position); + if (mention == null) + return; + Origin origin = mention.getOrigin(); + switch (origin.getType()) { + case Origin.ORIGIN_TYPE_LINK: + UIHelper.showUrlRedirect(getContext(), origin.getHref()); + break; + case Origin.ORIGIN_TYPE_SOFTWARE: + SoftwareDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_DISCUSS: + QuestionDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_BLOG: + BlogDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_TRANSLATION: + NewsDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_ACTIVE: + EventDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_NEWS: + NewsDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_TWEETS: + TweetDetailActivity.show(getContext(), origin.getId()); + break; + default: + // pass + } + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + return new UserMentionAdapter(this); + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/fragments/UserInfoFragment.java b/app/src/main/java/net/oschina/app/improve/user/fragments/UserInfoFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..a67b7e3d28cefa086bf7edc4a10a9972cf189b5e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/fragments/UserInfoFragment.java @@ -0,0 +1,621 @@ +package net.oschina.app.improve.user.fragments; + +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.account.activity.LoginActivity; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.main.synthesize.read.ReadHistoryActivity; +import net.oschina.app.improve.media.SelectImageActivity; +import net.oschina.app.improve.media.config.SelectOptions; +import net.oschina.app.improve.notice.NoticeBean; +import net.oschina.app.improve.notice.NoticeManager; +import net.oschina.app.improve.setting.SettingActivity; +import net.oschina.app.improve.share.ShareDialog; +import net.oschina.app.improve.user.activities.UserBlogActivity; +import net.oschina.app.improve.user.activities.UserFansActivity; +import net.oschina.app.improve.user.activities.UserFollowsActivity; +import net.oschina.app.improve.user.activities.UserMessageActivity; +import net.oschina.app.improve.user.activities.UserTweetActivity; +import net.oschina.app.improve.user.collection.UserCollectionActivity; +import net.oschina.app.improve.user.data.MyDataActivity; +import net.oschina.app.improve.user.event.UserEventActivity; +import net.oschina.app.improve.user.tags.UserTagsActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.utils.UiUtil; +import net.oschina.app.improve.widget.PortraitView; +import net.oschina.app.improve.widget.SolarSystemView; +import net.oschina.app.interf.OnTabReselectListener; +import net.oschina.app.ui.MyQRCodeDialog; +import net.oschina.app.util.ImageUtils; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.UIHelper; + +import java.io.File; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Random; + +import butterknife.Bind; +import butterknife.OnClick; +import cz.msebera.android.httpclient.Header; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * Created by fei on 2016/8/15. + *

    + * 用户个人界面 + */ + +public class UserInfoFragment extends BaseFragment implements View.OnClickListener, + EasyPermissions.PermissionCallbacks, NoticeManager.NoticeNotify, OnTabReselectListener { + + private ShareDialog mShareDialog; + + @Bind(R.id.iv_logo_setting) + ImageView mIvLogoSetting; + @Bind(R.id.iv_logo_zxing) + ImageView mIvLogoZxing; + @Bind(R.id.user_info_head_container) + FrameLayout mFlUserInfoHeadContainer; + + @Bind(R.id.iv_portrait) + PortraitView mPortrait; + @Bind(R.id.iv_gender) + ImageView mIvGander; + @Bind(R.id.user_info_icon_container) + FrameLayout mFlUserInfoIconContainer; + + @Bind(R.id.tv_nick) + TextView mTvName; + @Bind(R.id.tv_avail_score) + TextView mTextAvailScore; + @Bind(R.id.tv_active_score) + TextView mTextActiveScore; + @Bind(R.id.user_view_solar_system) + SolarSystemView mSolarSystem; + @Bind(R.id.rl_show_my_info) + LinearLayout mRlShowInfo; + + + @Bind(R.id.about_line) + View mAboutLine; + + @Bind(R.id.lay_about_info) + LinearLayout mLayAboutCount; + @Bind(R.id.tv_tweet) + TextView mTvTweetCount; + @Bind(R.id.tv_favorite) + TextView mTvFavoriteCount; + @Bind(R.id.tv_following) + TextView mTvFollowCount; + @Bind(R.id.tv_follower) + TextView mTvFollowerCount; + + @Bind(R.id.user_info_notice_message) + TextView mMesView; + + @Bind(R.id.user_info_notice_fans) + TextView mFansView; + + private boolean mIsUploadIcon; + private ProgressDialog mDialog; + + private int mMaxRadius; + private int mR; + private float mPx; + private float mPy; + + private File mCacheFile; + + private User mUserInfo; + + private TextHttpResponseHandler requestUserInfoHandler = new TextHttpResponseHandler() { + + @Override + public void onStart() { + super.onStart(); + if (mSolarSystem != null) mSolarSystem.accelerate(); + if (mIsUploadIcon) { + showWaitDialog(R.string.title_updating_user_avatar); + } + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString + , Throwable throwable) { + if (mIsUploadIcon) { + Toast.makeText(getActivity(), R.string.title_update_fail_status, Toast.LENGTH_SHORT).show(); + deleteCacheImage(); + } + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + User userInfo = (User) resultBean.getResult(); + updateView(userInfo); + //缓存用户信息 + AccountHelper.updateUserCache(userInfo); + } + if (mIsUploadIcon) { + deleteCacheImage(); + } + } catch (Exception e) { + e.printStackTrace(); + onFailure(statusCode, headers, responseString, e); + } + } + + @Override + public void onFinish() { + super.onFinish(); + if (mSolarSystem != null) mSolarSystem.decelerate(); + if (mIsUploadIcon) mIsUploadIcon = false; + if (mDialog != null && mDialog.isShowing()) mDialog.dismiss(); + } + }; + + + /** + * delete the cache image file for upload action + */ + @SuppressWarnings("ResultOfMethodCallIgnored") + private void deleteCacheImage() { + File file = this.mCacheFile; + if (file != null && file.exists()) { + file.delete(); + } + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_main_user_home; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + measureTitleBarHeight(); + + if (mFansView != null) + mFansView.setVisibility(View.INVISIBLE); + + initSolar(); + + } + + @Override + protected void initData() { + super.initData(); + NoticeManager.bindNotify(this); + requestUserCache(); + } + + @Override + public void onResume() { + super.onResume(); + mIsUploadIcon = false; + if (AccountHelper.isLogin()) { + User user = AccountHelper.getUser(); + updateView(user); + } else { + hideView(); + } + } + + /** + * if user isLogin,request user cache, + * And then request user info and update user info + */ + private void requestUserCache() { + if (AccountHelper.isLogin()) { + User user = AccountHelper.getUser(); + updateView(user); + sendRequestData(); + } else { + hideView(); + } + } + + @Override + public void onPause() { + super.onPause(); + if (!AccountHelper.isLogin()) { + hideView(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + NoticeManager.unBindNotify(this); + } + + /** + * update the view + * + * @param userInfo userInfo + */ + private void updateView(User userInfo) { + mPortrait.setup(userInfo); + mPortrait.setVisibility(View.VISIBLE); + + mTvName.setText(userInfo.getName()); + mTvName.setVisibility(View.VISIBLE); + mTvName.setTextSize(20.0f); + + switch (userInfo.getGender()) { + case 0: + mIvGander.setVisibility(View.INVISIBLE); + break; + case 1: + mIvGander.setVisibility(View.VISIBLE); + mIvGander.setImageResource(R.mipmap.ic_male); + break; + case 2: + mIvGander.setVisibility(View.VISIBLE); + mIvGander.setImageResource(R.mipmap.ic_female); + break; + default: + break; + } + + mTextAvailScore.setText(String.format("技能积分 %s", userInfo.getStatistics().getHonorScore())); + mTextActiveScore.setText(String.format("活跃积分 %s", userInfo.getStatistics().getActiveScore())); + mTextAvailScore.setVisibility(View.VISIBLE); + mTextActiveScore.setVisibility(View.VISIBLE); + mAboutLine.setVisibility(View.VISIBLE); + mLayAboutCount.setVisibility(View.VISIBLE); + mTvTweetCount.setText(formatCount(userInfo.getStatistics().getTweet())); + mTvFavoriteCount.setText(formatCount(userInfo.getStatistics().getCollect())); + mTvFollowCount.setText(formatCount(userInfo.getStatistics().getFollow())); + mTvFollowerCount.setText(formatCount(userInfo.getStatistics().getFans())); + + mUserInfo = userInfo; + } + + /** + * format count + * + * @param count count + * @return formatCount + */ + private String formatCount(long count) { + + if (count > 1000) { + int a = (int) (count / 100); + int b = a % 10; + int c = a / 10; + String str; + if (c <= 9 && b != 0) + str = c + "." + b; + else + str = String.valueOf(c); + + return str + "k"; + } else { + return String.valueOf(count); + } + + } + + /** + * requestData + */ + private void sendRequestData() { + if (TDevice.hasInternet() && AccountHelper.isLogin()) + OSChinaApi.getUserInfo(requestUserInfoHandler); + } + + /** + * init solar view + */ + private void initSolar() { + View root = this.mRoot; + if (root != null) { + root.post(new Runnable() { + @Override + public void run() { + + if (mRlShowInfo == null) return; + int width = mRlShowInfo.getWidth(); + float rlShowInfoX = mRlShowInfo.getX(); + + int height = mFlUserInfoIconContainer.getHeight(); + float y1 = mFlUserInfoIconContainer.getY(); + + float x = mPortrait.getX(); + float y = mPortrait.getY(); + int portraitWidth = mPortrait.getWidth(); + + mPx = x + +rlShowInfoX + (width >> 1); + mPy = y1 + y + (height - y) / 2; + mMaxRadius = (int) (mSolarSystem.getHeight() - mPy + 250); + mR = (portraitWidth >> 1); + + updateSolar(mPx, mPy); + + } + }); + } + } + + /** + * update solar + * + * @param px float + * @param py float + */ + private void updateSolar(float px, float py) { + + SolarSystemView solarSystemView = mSolarSystem; + Random random = new Random(System.currentTimeMillis()); + int maxRadius = mMaxRadius; + int r = mR; + solarSystemView.clear(); + for (int i = 40, radius = r + i; radius <= maxRadius; i = (int) (i * 1.4), radius += i) { + SolarSystemView.Planet planet = new SolarSystemView.Planet(); + planet.setClockwise(random.nextInt(10) % 2 == 0); + planet.setAngleRate((random.nextInt(35) + 1) / 1000.f); + planet.setRadius(radius); + solarSystemView.addPlanets(planet); + + } + solarSystemView.setPivotPoint(px, py); + } + + /** + * + */ + private void hideView() { + mPortrait.setImageResource(R.mipmap.widget_default_face); + mTvName.setText(R.string.user_hint_login); + mTvName.setTextSize(16.0f); + mIvGander.setVisibility(View.INVISIBLE); + mTextActiveScore.setVisibility(View.INVISIBLE); + mTextAvailScore.setVisibility(View.INVISIBLE); + mLayAboutCount.setVisibility(View.GONE); + mAboutLine.setVisibility(View.GONE); + } + + /** + * measureTitleBarHeight + */ + private void measureTitleBarHeight() { + if (mRlShowInfo != null) { + mRlShowInfo.setPadding(mRlShowInfo.getLeft(), + UiUtil.getStatusBarHeight(getContext()), + mRlShowInfo.getRight(), mRlShowInfo.getBottom()); + } + } + + @SuppressWarnings("deprecation") + @OnClick({ + R.id.iv_logo_setting, R.id.iv_logo_zxing, R.id.iv_portrait, + R.id.user_view_solar_system, R.id.ly_tweet, R.id.ly_favorite, + R.id.ly_following, R.id.ly_follower, R.id.rl_message, + R.id.rl_blog, R.id.rl_read, R.id.rl_share, R.id.rl_info_tags, + R.id.rl_info_question, R.id.rl_info_activities, R.id.rl_team + }) + @Override + public void onClick(View v) { + + int id = v.getId(); + + if (id == R.id.iv_logo_setting) { + SettingActivity.show(mContext); + } else { + + if (!AccountHelper.isLogin()) { + LoginActivity.show(getActivity()); + return; + } + + switch (id) { + case R.id.iv_logo_zxing: + MyQRCodeDialog dialog = new MyQRCodeDialog(getActivity()); + dialog.show(); + break; + case R.id.iv_portrait: + //查看头像 or 更换头像 + showAvatarOperation(); + break; + case R.id.user_view_solar_system: + //显示我的资料 + if (mUserInfo != null) { + MyDataActivity.show(mContext, mUserInfo); + } + break; + case R.id.ly_tweet: + UserTweetActivity.show(getActivity(), AccountHelper.getUserId()); + break; + case R.id.ly_favorite: + UserCollectionActivity.show(getActivity()); + break; + case R.id.ly_following: + UserFollowsActivity.show(getActivity(), AccountHelper.getUserId()); + break; + case R.id.ly_follower: + UserFansActivity.show(getActivity(), AccountHelper.getUserId()); + break; + case R.id.rl_message: + UserMessageActivity.show(getActivity()); + break; + case R.id.rl_blog: + UserBlogActivity.show(mContext, AccountHelper.getUserId()); + break; + case R.id.rl_info_question: + UIHelper.showUserQuestion(getActivity(), AccountHelper.getUserId()); + break; + case R.id.rl_info_activities: + UserEventActivity.show(mContext, AccountHelper.getUserId(), ""); + break; + case R.id.rl_team: + UIHelper.showTeamMainActivity(getActivity()); + break; + case R.id.rl_read: + ReadHistoryActivity.show(mContext); + break; + case R.id.rl_info_tags: + UserTagsActivity.show(mContext); + break; + case R.id.rl_share: + if (mShareDialog == null) { + mShareDialog = new ShareDialog(mContext); + String title = "开发者必备的手机 App,网罗全网技术内容"; + mShareDialog.setTitle(title); + mShareDialog.init(getActivity(), title, "开源中国 App 4.0 全新上线,全网技术内容一手搞定,只关注我感兴趣的内容,点我查看详细介绍", "https://www.oschina.net/app_phone"); + mShareDialog.setShareApp(true); + } + mShareDialog.show(); + break; + default: + break; + } + } + } + + /** + * 更换头像 or 查看头像 + */ + private void showAvatarOperation() { + if (!AccountHelper.isLogin()) { + LoginActivity.show(getActivity()); + } else { + DialogHelper.getSelectDialog(getActivity(), + getString(R.string.action_select), + getResources().getStringArray(R.array.avatar_option), "取消", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + switch (i) { + case 0: + SelectImageActivity.show(getContext(), new SelectOptions.Builder() + .setSelectCount(1) + .setHasCam(true) + .setCrop(700, 700) + .setCallback(new SelectOptions.Callback() { + @Override + public void doSelected(String[] images) { + String path = images[0]; + uploadNewPhoto(new File(path)); + } + }).build()); + break; + + case 1: + if (mUserInfo == null + || TextUtils.isEmpty(mUserInfo.getPortrait())) return; + UIHelper.showUserAvatar(getActivity(), mUserInfo.getPortrait()); + break; + } + } + }).show(); + } + } + + /** + * take photo + */ + private void startTakePhoto() { + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + startActivityForResult(intent, + ImageUtils.REQUEST_CODE_GETIMAGE_BYCAMERA); + } + + public ProgressDialog showWaitDialog(int messageId) { + String message = getResources().getString(messageId); + if (mDialog == null) { + mDialog = DialogHelper.getProgressDialog(getActivity(), message); + } + + mDialog.setMessage(message); + mDialog.show(); + + return mDialog; + } + + /** + * update the new picture + */ + private void uploadNewPhoto(File file) { + // 获取头像缩略图 + if (file == null || !file.exists() || file.length() == 0) { + AppContext.showToast(getString(R.string.title_icon_null)); + } else { + mIsUploadIcon = true; + this.mCacheFile = file; + OSChinaApi.updateUserIcon(file, requestUserInfoHandler); + } + + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + try { + startTakePhoto(); + } catch (Exception e) { + Toast.makeText(this.getContext(), R.string.permissions_camera_error, Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + Toast.makeText(this.getContext(), R.string.permissions_camera_error, Toast.LENGTH_LONG).show(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions + , @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + @Override + public void onNoticeArrived(NoticeBean bean) { + if (mMesView != null) { + int allCount = bean.getReview() + bean.getLetter() + bean.getMention(); + mMesView.setVisibility(allCount > 0 ? View.VISIBLE : View.GONE); + mMesView.setText(String.valueOf(allCount)); + } + if (mFansView != null) { + int fans = bean.getFans(); + mFansView.setVisibility(fans > 0 ? View.VISIBLE : View.GONE); + mFansView.setText(String.valueOf(fans)); + } + } + + + @Override + public void onTabReselect() { + sendRequestData(); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/user/fragments/UserMentionFragment.java b/app/src/main/java/net/oschina/app/improve/user/fragments/UserMentionFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..c779e7bc8e7aea69dce2d9b63a40da77c7933025 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/fragments/UserMentionFragment.java @@ -0,0 +1,102 @@ +package net.oschina.app.improve.user.fragments; + +import android.content.Context; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseRecyclerViewFragment; +import net.oschina.app.improve.bean.Mention; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.Origin; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.tweet.activities.TweetDetailActivity; +import net.oschina.app.improve.user.activities.UserMessageActivity; +import net.oschina.app.improve.user.adapter.UserMentionAdapter; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.UIHelper; + +import java.lang.reflect.Type; + +/** + * Created by huanghaibin_dev + * on 2016/8/16. + */ + +public class UserMentionFragment extends BaseRecyclerViewFragment { + + private UserMessageActivity activity; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context != null && context instanceof UserMessageActivity) { + activity = (UserMessageActivity) context; + } + } + + @Override + protected void onRequestSuccess(int code) { + super.onRequestSuccess(code); + if (activity != null && isRefreshing) activity.onRequestSuccess(0); + } + + @Override + protected void requestData() { + super.requestData(); + OSChinaApi.getMsgMentionList(isRefreshing ? null : mBean.getNextPageToken(), mHandler); + } + + @Override + public void onItemClick(int position, long itemId) { + Mention mention = mAdapter.getItem(position); + if (mention == null) return; + Origin origin = mention.getOrigin(); + if (origin == null) return; + switch (origin.getType()) { + case Origin.ORIGIN_TYPE_LINK: + UIHelper.showUrlRedirect(getContext(), origin.getHref()); + break; + case Origin.ORIGIN_TYPE_SOFTWARE: + SoftwareDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_DISCUSS: + QuestionDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_BLOG: + BlogDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_TRANSLATION: + NewsDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_ACTIVE: + EventDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_NEWS: + NewsDetailActivity.show(getContext(), origin.getId()); + break; + case Origin.ORIGIN_TYPE_TWEETS: + TweetDetailActivity.show(getContext(), origin.getId()); + break; + default: + SimplexToast.show(getContext(), "不支持该类型"); + } + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + return new UserMentionAdapter(this); + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/fragments/UserMessageFragment.java b/app/src/main/java/net/oschina/app/improve/user/fragments/UserMessageFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..0c55b6bdaf6652305eeceb6166dc4d7b41be06be --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/fragments/UserMessageFragment.java @@ -0,0 +1,88 @@ +package net.oschina.app.improve.user.fragments; + +import android.content.Context; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseRecyclerViewFragment; +import net.oschina.app.improve.bean.Message; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.user.activities.UserMessageActivity; +import net.oschina.app.improve.user.activities.UserSendMessageActivity; +import net.oschina.app.improve.user.adapter.UserMessageAdapter; + +import java.lang.reflect.Type; + +/** + * Created by huanghaibin_dev + * on 2016/8/16. + */ + +public class UserMessageFragment extends BaseRecyclerViewFragment { + + public long authorId; + + private UserMessageActivity activity; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context != null && context instanceof UserMessageActivity) { + activity = (UserMessageActivity) context; + } + } + + @Override + protected void onRequestSuccess(int code) { + super.onRequestSuccess(code); + if (activity != null && isRefreshing) activity.onRequestSuccess(2); + } + + @Override + public void initData() { + super.initData(); + authorId = AccountHelper.getUserId(); + } + + @Override + protected void requestData() { + super.requestData(); + OSChinaApi.getUserMessageList(isRefreshing ? null : mBean.getNextPageToken(), mHandler); + } + + @Override + public void onItemClick(int position, long itemId) { + Message message = mAdapter.getItem(position); + if (message == null) + return; + User sender = message.getSender(); + if (sender != null) + UserSendMessageActivity.show(getContext(), message.getSender()); + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + return new UserMessageAdapter(this); + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/fragments/UserQuestionFragment.java b/app/src/main/java/net/oschina/app/improve/user/fragments/UserQuestionFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..45ee8844bf570b521be76aa61ee53d00128e1789 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/fragments/UserQuestionFragment.java @@ -0,0 +1,77 @@ +package net.oschina.app.improve.user.fragments; + + +import android.os.Bundle; +import android.support.v4.app.Fragment; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseRecyclerViewFragment; +import net.oschina.app.improve.bean.Question; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.user.adapter.UserQuestionAdapter; + +import java.lang.reflect.Type; + +/** + * @author thanatosx + */ +public class UserQuestionFragment extends BaseRecyclerViewFragment { + + public static final String HISTORY_MY_QUESTION = "history_my_question"; + public static final String BUNDLE_KEY_AUTHOR_ID = "author_id"; + private long userId; + + public static Fragment instantiate(int authorId) { + UserQuestionFragment fragment = new UserQuestionFragment(); + Bundle bundle = new Bundle(); + bundle.putLong(BUNDLE_KEY_AUTHOR_ID, authorId); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + userId = bundle.getLong(BUNDLE_KEY_AUTHOR_ID, 0); + } + + @Override + protected void requestData() { + String token = isRefreshing ? null : mBean.getNextPageToken(); + OSChinaApi.getUserQuestionList(token, userId, mHandler); + } + + @Override + protected BaseRecyclerAdapter getRecyclerAdapter() { + return new UserQuestionAdapter(getContext()); + } + + @Override + protected Type getType() { + return new TypeToken>>() { + }.getType(); + } + + @Override + protected boolean isNeedCache() { + return false; + } + + @Override + protected boolean isNeedEmptyView() { + return false; + } + + @Override + public void onItemClick(int position, long itemId) { + Question question = mAdapter.getItem(position); + if (question == null) + return; + QuestionDetailActivity.show(getActivity(), question.getId()); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/helper/ContactsCacheManager.java b/app/src/main/java/net/oschina/app/improve/user/helper/ContactsCacheManager.java new file mode 100644 index 0000000000000000000000000000000000000000..3f641ded3e743042b66316bddc2972a7ead45332 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/helper/ContactsCacheManager.java @@ -0,0 +1,318 @@ +package net.oschina.app.improve.user.helper; + +import android.content.Context; +import android.text.TextUtils; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.AppContext; +import net.oschina.app.OSCApplication; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.User; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.utils.CacheManager; +import net.oschina.app.improve.utils.parser.RichTextParser; +import net.oschina.app.util.TDevice; + +import java.io.File; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ +@SuppressWarnings({"WeakerAccess", "unused"}) +public class ContactsCacheManager { + private static final String USER_CACHE_NAME = "UserContactsCache"; + private static final String RECENT_CACHE_FILE = "RecentContactsCache"; + + public static LinkedList getRecentCache(Context context) { + List cache = CacheManager.readListJson(context, RECENT_CACHE_FILE, User.class); + LinkedList linkedList = new LinkedList<>(); + if (cache != null) + linkedList.addAll(cache); + return linkedList; + } + + public static void addRecentCache(Author... authors) { + if (authors == null || authors.length == 0) + return; + ContactsCacheManager.addRecentCache(AppContext.getInstance(), authors); + } + + private static void addRecentCache(Context context, Author... authors) { + final LinkedList localCache = getRecentCache(context); + + // 避免重复添加 + for (Author author : authors) { + if (author == null + || author.getId() <= 0 + || TextUtils.isEmpty(author.getName()) + || author.getId() == AccountHelper.getUserId()) + continue; + if (checkInContacts(localCache, author)) { + // 移除后添加到头部 + int index = indexOfContacts(localCache, author); + if (index >= 0) { + localCache.remove(index); + localCache.addFirst(author); + } + } else { + localCache.addFirst(author); + } + } + + // 至多存储15条 + while (localCache.size() > 10) { + localCache.removeLast(); + } + + CacheManager.saveToJson(context, RECENT_CACHE_FILE, localCache); + } + + public static int indexOfContacts(List list, Author user) { + for (Author author : list) { + if (author.getId() == user.getId()) + return list.indexOf(author); + } + return -1; + } + + public static boolean checkInContacts(List list, Author user) { + if (list == null || user == null) + return false; + for (Author author : list) { + if (author.getId() == user.getId()) + return true; + } + return false; + } + + public static ArrayList getContacts() { + return CacheManager.readListJson(AppContext.getInstance(), USER_CACHE_NAME, Author.class); + } + + /** + * 同步当前用户联系人信息 + */ + public static void sync() { + SyncHelper.sync(null); + } + + public static void sync(Runnable runnable) { + SyncHelper.sync(runnable); + } + + public static final String SPLIT_HEAD = ""; + public static final String DEFAULT_CHAR = "#"; + + public static List sortToFriendModel(List list) { + ArrayList friends = new ArrayList<>(); + + if (list == null || list.size() <= 0) { + return friends; + } + + for (Author author : list) { + if (author == null || TextUtils.isEmpty(author.getName())) + continue; + String name = author.getName(); + if (TextUtils.isEmpty(name)) continue; + + String pinyin = RichTextParser.convertToPinyin(name, SPLIT_HEAD); + String firstChar = pinyin.substring(0, 1).toUpperCase(); + firstChar = firstChar.matches("[A-Z]") ? firstChar : DEFAULT_CHAR; + + Friend friend = new Friend(author); + friend.pinyin = pinyin; + friend.firstChar = firstChar; + + friends.add(friend); + } + + // 排序 + Collections.sort(friends, new Comparator() { + @Override + public int compare(Friend o1, Friend o2) { + if (o1.firstChar.equals(DEFAULT_CHAR) || o2.firstChar.equals(DEFAULT_CHAR)) { + return o2.firstChar.compareTo(o1.firstChar); + } + return o1.firstChar.compareTo(o2.firstChar); + } + }); + + return friends; + } + + + @SuppressWarnings("WeakerAccess") + public static class Friend { + public Friend(Author author) { + this.author = author; + } + + public Friend(Author author, String firstChar) { + this.author = author; + this.firstChar = firstChar; + } + + public Author author; + public String pinyin = ""; + public String firstChar = "↑"; + public boolean isSelected; + + @Override + public String toString() { + return "Friend{" + + "author=" + author + + ", pinyin='" + pinyin + '\'' + + ", firstChar='" + firstChar + '\'' + + ", isSelected=" + isSelected + + '}'; + } + } + + public interface SelectedTrigger { + void trigger(T t, boolean selected); + + void trigger(Author author, boolean selected); + } + + public interface OnSelectedChangeListener { + void tryTriggerSelected(Friend t, SelectedTrigger trigger); + } + + @SuppressWarnings("WeakerAccess") + private final static class SyncHelper { + private static SyncHelper INSTANCE = new SyncHelper(); + private boolean isSyncing = false; + private final List notifies = new LinkedList<>(); + + private SyncHelper() { + } + + private void syncUserFriends(final List list, final PageBean pageBean) { + //检查网络 + if (!TDevice.hasInternet() && !AccountHelper.isLogin()) { + return; + } + + long userId = AccountHelper.getUserId(); + OSChinaApi.getUserFriends(userId, pageBean == null ? null : pageBean.getNextPageToken(), + OSChinaApi.REQUEST_COUNT, new TextHttpResponseHandler() { + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + syncDone(list); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String + responseString) { + + Type type = new TypeToken>>() { + }.getType(); + + ResultBean> resultBean = AppOperator.getGson().fromJson(responseString, type); + if (resultBean.isSuccess()) { + try { + List fansOrFollows = resultBean.getResult().getItems(); + int size = fansOrFollows.size(); + if (size > 0) { + list.addAll(fansOrFollows); + syncUserFriends(list, resultBean.getResult()); + return; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + syncDone(list); + } + }); + } + + + private void syncDone(final List list) { + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + saveCache(list); + notifyAllCallback(); + isSyncing = false; + } + }); + } + + private void saveCache(List list) { + CacheManager.saveToJson(OSCApplication.getInstance(), USER_CACHE_NAME, list); + } + + public static void sync(Runnable callback) { + // 没有网络直接返回 + if (!TDevice.hasInternet()) { + callback.run(); + return; + } + + if (callback != null) { + synchronized (INSTANCE.notifies) { + INSTANCE.notifies.add(callback); + } + } + + if (INSTANCE.checkNeedLoadFromNet()) { + if (INSTANCE.isSyncing) + return; + INSTANCE.isSyncing = true; + INSTANCE.syncUserFriends(new ArrayList(), null); + } else { + INSTANCE.notifyAllCallback(); + } + } + + + // 检查网络状态和缓存有效期 + private boolean checkNeedLoadFromNet() { + String path = String.format("%s/%s.json", AppContext.getInstance().getCacheDir(), USER_CACHE_NAME); + File file = new File(path); + + if (file.exists() && file.isFile()) { + long lastModified = file.lastModified(); + long millis = Calendar.getInstance().getTimeInMillis(); + //天数计算 + long updateTime = (millis - lastModified) / (1000 * 60 * 60 * 24); + //默认wifi模式下当进入联系人界面,可以2天缓存一次。其他情况下10天缓存一次 + if (TDevice.isWifiOpen()) { + return updateTime >= 2; + } else { + return updateTime >= 10; + } + } + + return true; + } + + private void notifyAllCallback() { + synchronized (notifies) { + for (Runnable notify : notifies) { + notify.run(); + } + notifies.clear(); + } + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/sign/InvitationActivity.java b/app/src/main/java/net/oschina/app/improve/user/sign/InvitationActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..0ef5275b90b632518defac39d84acd3e790fbd36 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/sign/InvitationActivity.java @@ -0,0 +1,173 @@ +package net.oschina.app.improve.user.sign; + +import android.Manifest; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Bitmap; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.view.View; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.target.Target; + +import net.oschina.app.R; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.activities.BaseBackActivity; +import net.oschina.app.improve.dialog.ShareDialog; +import net.oschina.app.improve.media.ImageGalleryActivity; +import net.oschina.app.improve.utils.DialogHelper; + +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * 邀请函 + * Created by haibin on 2017/4/11. + */ +public class InvitationActivity extends BackActivity implements View.OnClickListener, + EasyPermissions.PermissionCallbacks { + private ShareDialog mShareDialog; + @Bind(R.id.iv_invitation) + ImageView mImageInvitation; + private String mUrl; + private ProgressDialog mWaitDialog; + + public static void show(Context context, String url) { + Intent intent = new Intent(context, InvitationActivity.class); + intent.putExtra("url", url); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_invitation; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mShareDialog = new ShareDialog(this, -1, false); + mUrl = getIntent().getStringExtra("url"); + } + + @Override + protected void initData() { + super.initData(); + tryLoadBitmap(); + } + + private void tryLoadBitmap() { + getImageLoader().load(mUrl) + .asBitmap() + .fitCenter() + .into(mImageInvitation); + } + + @OnClick({R.id.btn_share, R.id.iv_invitation}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_share: + saveToFileByPermission(); + break; + case R.id.iv_invitation: + ImageGalleryActivity.show(this, mUrl); + break; + } + } + + private void showWaitDialog() { + if (mWaitDialog == null) { + mWaitDialog = DialogHelper.getProgressDialog(this); + mWaitDialog.setMessage("正在加载图片"); + mWaitDialog.setCancelable(true); + } + mWaitDialog.show(); + } + + private void hideDialog() { + if (mWaitDialog == null) return; + mWaitDialog.dismiss(); + } + + @Override + protected void onPause() { + super.onPause(); + if (mShareDialog != null) { + mShareDialog.dismiss(); + } + } + + private static final int PERMISSION_ID = 0x0001; + + @SuppressWarnings("unused") + @AfterPermissionGranted(PERMISSION_ID) + public void saveToFileByPermission() { + String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + if (EasyPermissions.hasPermissions(this, permissions)) { + showWaitDialog(); + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + try { + final Bitmap thumbBitmap = + Glide.with(InvitationActivity.this) + .load(mUrl) + .asBitmap() + .into(720, 1280).get(); + runOnUiThread(new Runnable() { + @Override + public void run() { + hideDialog(); + mShareDialog.bitmap(thumbBitmap); + mShareDialog.show(); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } else { + EasyPermissions.requestPermissions(this, "请授予文件读写权限", PERMISSION_ID, permissions); + } + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + DialogHelper.getConfirmDialog(this, "", "没有权限, 你需要去设置中开启读取手机存储权限.", "去设置", "取消", false, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); + //finish(); + } + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + //finish(); + } + }).show(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/sign/in/SignInInfoActivity.java b/app/src/main/java/net/oschina/app/improve/user/sign/in/SignInInfoActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..c27137e864b07dd05a73a0b959fa287090ac8606 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/sign/in/SignInInfoActivity.java @@ -0,0 +1,70 @@ +package net.oschina.app.improve.user.sign.in; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.EventSignIn; +import net.oschina.app.improve.bean.SubBean; + +import butterknife.Bind; + +/** + * 签到状态信息界面 + * Created by haibin on 2017/4/12. + */ + +public class SignInInfoActivity extends BackActivity { + @Bind(R.id.tv_event_name) + TextView mTextEventName; + @Bind(R.id.tv_cost) + TextView mTextCost; + @Bind(R.id.tv_cost_msg) + TextView mTextCostMsg; + @Bind(R.id.tv_msg) + TextView mTextMsg; + + public static void show(Context context, SubBean detail, EventSignIn info) { + Intent intent = new Intent(context, SignInInfoActivity.class); + Bundle bundle = new Bundle(); + bundle.putSerializable("detail", detail); + bundle.putSerializable("sign_in", info); + intent.putExtras(bundle); + context.startActivity(intent); + } + + @Override + protected int getContentView() { + return R.layout.activity_sign_in_info; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + } + + @SuppressLint("SetTextI18n") + @Override + protected void initData() { + super.initData(); + Bundle bundle = getIntent().getExtras(); + if (bundle == null) { + finish(); + return; + } + SubBean detail = (SubBean) bundle.getSerializable("detail"); + EventSignIn info = (EventSignIn) bundle.getSerializable("sign_in"); + assert detail != null; + mTextEventName.setText(detail.getTitle()); + assert info != null; + mTextCost.setText("¥" + info.getCost() / 100); + mTextCostMsg.setText(info.getCostMessage()); + mTextMsg.setText(info.getMessage()); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/sign/up/SignUpContract.java b/app/src/main/java/net/oschina/app/improve/user/sign/up/SignUpContract.java new file mode 100644 index 0000000000000000000000000000000000000000..bef76f3fd68f37d27a93d12a2fa400d38eb7577f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/sign/up/SignUpContract.java @@ -0,0 +1,50 @@ +package net.oschina.app.improve.user.sign.up; + +import net.oschina.app.improve.base.BasePresenter; +import net.oschina.app.improve.base.BaseView; +import net.oschina.app.improve.bean.EventDetail; +import net.oschina.app.improve.bean.EventSignIn; +import net.oschina.app.improve.bean.SubBean; + +import java.util.Map; + +/** + * 活动报名签到,包括报名信息 + * Created by haibin on 2017/4/12. + */ + +interface SignUpContract { + interface EmptyView { + void hideEmptyLayout(); + + void showErrorLayout(int errorType); + } + + interface View extends BaseView { + void showGetDetailSuccess(SubBean detail); + + void showGetApplyInfoSuccess(Map map); + + void showSignInSuccess(EventSignIn sign); + + void showSignInFailure(int strId); + + void showCancelApplySuccess(String message); + + void showCancelApplyFailure(String message); + } + + interface Presenter extends BasePresenter { + void getEventDetail(long id); + + void getApplyInfo(long id); + + void cancelApply(long id); + /** + * 签到 + * + * @param id 活动id + */ + void signUp(long id); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/sign/up/SignUpFragment.java b/app/src/main/java/net/oschina/app/improve/user/sign/up/SignUpFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..978403df10f564edace14afe387849f9f307fc0c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/sign/up/SignUpFragment.java @@ -0,0 +1,195 @@ +package net.oschina.app.improve.user.sign.up; + +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.bean.EventSignIn; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.user.sign.InvitationActivity; +import net.oschina.app.improve.user.sign.in.SignInInfoActivity; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.SimplexToast; + +import java.util.HashMap; +import java.util.Map; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 报名信息 + * Created by haibin on 2017/4/13. + */ + +public class SignUpFragment extends BaseFragment implements View.OnClickListener, SignUpContract.View { + @Bind(R.id.tv_event_name) + TextView mTextEventName; + @Bind(R.id.tv_name) + TextView mTextName; + @Bind(R.id.tv_work) + TextView mTextWork; + @Bind(R.id.tv_date) + TextView mTextDate; + @Bind(R.id.tv_phone) + TextView mTextPhone; + @Bind(R.id.tv_company) + TextView mTextCompany; + @Bind(R.id.tv_status) + TextView mTextStatus; + @Bind(R.id.tv_remark) + TextView mTextRemark; + @Bind(R.id.btn_sign_up) + Button mBtnSignUp; + @Bind(R.id.btn_cancel) + Button mBtnCalcen; + private SubBean mDetail; + private String mInvitationImg; + private SignUpContract.Presenter mPresenter; + /** + * mType 从活动详情进来表示查看邀请函==1,从扫一扫进来表示要签到 == 2 + */ + private int mType; + + static SignUpFragment newInstance(int type) { + Bundle bundle = new Bundle(); + bundle.putInt("type", type); + SignUpFragment fragment = new SignUpFragment(); + fragment.setArguments(bundle); + return fragment; + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_sign_in; + } + + @Override + protected void initBundle(Bundle bundle) { + super.initBundle(bundle); + mType = bundle.getInt("type", 1); + } + + @OnClick({R.id.btn_sign_up, R.id.btn_cancel}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_sign_up: + switch (mType) { + case 1: + InvitationActivity.show(mContext, mInvitationImg); + break; + case 2: + mPresenter.signUp(mDetail.getId()); + break; + } + break; + case R.id.btn_cancel: + DialogHelper.getConfirmDialog(mContext, "", "是否确认取消?", "是", "否", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mPresenter.cancelApply(mDetail.getId()); + } + }).show(); + break; + } + } + + @Override + protected void initData() { + super.initData(); + mBtnSignUp.setText(mType == 1 ? "活动邀请函" : "立即签到"); + mBtnCalcen.setVisibility(mType == 1 ? View.VISIBLE : View.GONE); + } + + @Override + public void showNetworkError(int strId) { + if (mContext == null) + return; + SimplexToast.show(mContext, strId); + } + + @Override + public void showGetDetailSuccess(SubBean detail) { + mDetail = detail; + mTextEventName.setText(detail.getTitle()); + HashMap extra = mDetail.getExtra(); + if (extra != null) { + int eventType = getExtraInt(extra.get("eventType")); + mBtnSignUp.setVisibility(eventType == 1 ? View.VISIBLE : View.GONE); + if (eventType != 1) { + mBtnCalcen.setVisibility(View.VISIBLE); + mBtnCalcen.setBackgroundResource(R.drawable.selector_event_sign); + mBtnCalcen.setTextColor(Color.WHITE); + } + } + } + + @Override + public void showGetApplyInfoSuccess(Map map) { + mTextName.setText(getExtraString("姓名", map)); + mTextWork.setText(getExtraString("职位", map)); + mTextDate.setText(getExtraString("报名时间", map)); + mTextPhone.setText(getExtraString("手机号码", map)); + mTextCompany.setText(getExtraString("公司", map)); + mTextStatus.setText(getExtraString("状态", map)); + mInvitationImg = getExtraString("invitationImg", map); + String remark = getExtraString("备注", map); + if (TextUtils.isEmpty(remark)) { + setGone(R.id.ll_remark); + return; + } + mTextRemark.setText(remark); + + } + + @Override + public void showSignInSuccess(EventSignIn sign) { + SignInInfoActivity.show(mContext, mDetail, sign); + } + + @Override + public void showSignInFailure(int strId) { + if (mContext == null) + return; + SimplexToast.show(mContext, strId); + } + + @Override + public void showCancelApplySuccess(String message) { + SimplexToast.show(mContext, message); + Intent intent = new Intent(); + getActivity().setResult(Activity.RESULT_OK, intent); + getActivity().finish(); + } + + @Override + public void showCancelApplyFailure(String message) { + SimplexToast.show(mContext, message); + } + + private String getExtraString(String key, Map map) { + if (map.containsKey(key)) { + return map.get(key); + } + return ""; + } + + private int getExtraInt(Object object) { + return object == null ? 0 : Double.valueOf(object.toString()).intValue(); + } + + + @Override + public void setPresenter(SignUpContract.Presenter presenter) { + this.mPresenter = presenter; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/sign/up/SignUpInfoActivity.java b/app/src/main/java/net/oschina/app/improve/user/sign/up/SignUpInfoActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..ef675a2b9d090ba2aa7e2e7bb4e040e9a90756ce --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/sign/up/SignUpInfoActivity.java @@ -0,0 +1,73 @@ +package net.oschina.app.improve.user.sign.up; + +import android.app.Activity; +import android.content.Intent; +import android.view.View; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.ui.empty.EmptyLayout; + +import butterknife.Bind; + +/** + * 活动报名签到界面,包括报名信息 + * Created by haibin on 2017/4/11. + */ + +public class SignUpInfoActivity extends BackActivity implements SignUpContract.EmptyView { + private SignUpContract.Presenter mPresenter; + @Bind(R.id.emptyLayout) + EmptyLayout mEmptyLayout; + + public static void show(Activity activity, long id, int type) { + Intent intent = new Intent(activity, SignUpInfoActivity.class); + intent.putExtra("id", id); + intent.putExtra("type", type); + activity.startActivityForResult(intent, 0x02); + } + + @Override + protected int getContentView() { + return R.layout.activity_sign_info; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + } + + @Override + protected void initData() { + super.initData(); + final long id = getIntent().getLongExtra("id", 0); + final int type = getIntent().getIntExtra("type", 1); + SignUpFragment fragment = SignUpFragment.newInstance(type); + addFragment(R.id.fl_content, fragment); + mPresenter = new SignUpPresenter(fragment, this); + mPresenter.getEventDetail(id); + mEmptyLayout.setOnLayoutClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mEmptyLayout.getErrorState() != EmptyLayout.NETWORK_LOADING) { + mEmptyLayout.setErrorType(EmptyLayout.NETWORK_LOADING); + mPresenter.getEventDetail(id); + } + } + }); + } + + @Override + public void hideEmptyLayout() { + if (mEmptyLayout != null) + mEmptyLayout.setErrorType(EmptyLayout.HIDE_LAYOUT); + } + + @Override + public void showErrorLayout(int errorType) { + if (mEmptyLayout != null) + mEmptyLayout.setErrorType(errorType); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/sign/up/SignUpPresenter.java b/app/src/main/java/net/oschina/app/improve/user/sign/up/SignUpPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..88702a692d5c385366b56493e3b51d58d513ecc8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/sign/up/SignUpPresenter.java @@ -0,0 +1,146 @@ +package net.oschina.app.improve.user.sign.up; + +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.EventSignIn; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.ui.empty.EmptyLayout; + +import java.util.Map; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2017/4/12. + */ + +class SignUpPresenter implements SignUpContract.Presenter { + private final SignUpContract.View mView; + private final SignUpContract.EmptyView mEmptyView; + + SignUpPresenter(SignUpContract.View mView, SignUpContract.EmptyView mEmptyView) { + this.mView = mView; + this.mEmptyView = mEmptyView; + this.mView.setPresenter(this); + } + + @Override + public void getEventDetail(final long id) { + OSChinaApi.getDetail(5, "", id, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean resultBean = AppOperator.createGson().fromJson(responseString, + new TypeToken>() { + }.getType()); + + if (resultBean.isSuccess()) { + mView.showGetDetailSuccess(resultBean.getResult()); + getApplyInfo(id); + } else { + mEmptyView.showErrorLayout(EmptyLayout.NODATA); + } + } catch (Exception e) { + e.printStackTrace(); + mEmptyView.showErrorLayout(EmptyLayout.NODATA); + } + } + }); + } + + @Override + public void getApplyInfo(long id) { + OSChinaApi.syncSignUserInfo(id, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mEmptyView.showErrorLayout(EmptyLayout.NODATA); + mView.showNetworkError(R.string.state_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> mapResultBean = AppOperator.createGson().fromJson(responseString, + new TypeToken>>() { + }.getType()); + if (mapResultBean.isSuccess()) { + mapResultBean.getResult(); + mView.showGetApplyInfoSuccess(mapResultBean.getResult()); + mEmptyView.hideEmptyLayout(); + } else { + mEmptyView.showErrorLayout(EmptyLayout.NODATA); + } + } catch (Exception e) { + e.printStackTrace(); + mEmptyView.showErrorLayout(EmptyLayout.NODATA); + } + } + }); + } + + @Override + public void signUp(long id) { + OSChinaApi.eventSignin(id, "", new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean result = AppOperator.createGson().fromJson(responseString, + new TypeToken>() { + }.getType()); + if (result.isSuccess()) { + EventSignIn eventSignIn = result.getResult(); + mView.showSignInSuccess(eventSignIn); + } else { + mView.showSignInFailure(R.string.event_sign_in_error); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showSignInFailure(R.string.event_sign_in_error); + } + } + }); + } + + @Override + public void cancelApply(long id) { + OSChinaApi.cancelApply(id, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showNetworkError(R.string.state_network_error); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean result = AppOperator.createGson().fromJson(responseString, + new TypeToken>() { + }.getType()); + if (result.getCode() == 1) { + mView.showCancelApplySuccess(result.getMessage()); + } else { + mView.showCancelApplyFailure(result.getMessage()); + } + } catch (Exception e) { + e.getMessage(); + mView.showCancelApplyFailure("取消报名失败"); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/tags/TagAdapter.java b/app/src/main/java/net/oschina/app/improve/user/tags/TagAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..64a45157123256f7c8103391043872fbf1e6450e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/tags/TagAdapter.java @@ -0,0 +1,54 @@ +package net.oschina.app.improve.user.tags; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Tags; + + class TagAdapter extends BaseRecyclerAdapter { + + private OnViewClickListener mDeleteListener; + + TagAdapter(Context context) { + super(context, NEITHER); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new TagHolder(mInflater.inflate(R.layout.item_list_tag, parent, false)); + } + + @Override + protected void onBindClickListener(RecyclerView.ViewHolder holder) { + TagHolder h = (TagHolder) holder; + h.mTextDelete.setTag(holder); + h.mTextDelete.setOnClickListener(mDeleteListener); + } + + public void setDeleteListener(OnViewClickListener mDeleteListener) { + this.mDeleteListener = mDeleteListener; + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Tags item, int position) { + TagHolder h = (TagHolder) holder; + h.mTextTag.setText(item.getName()); + } + + private static final class TagHolder extends RecyclerView.ViewHolder { + private TextView mTextTag; + private TextView mTextDelete; + + + private TagHolder(View itemView) { + super(itemView); + mTextTag = (TextView) itemView.findViewById(R.id.tv_tag); + mTextDelete = (TextView) itemView.findViewById(R.id.tv_delete); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/tags/UserTagsActivity.java b/app/src/main/java/net/oschina/app/improve/user/tags/UserTagsActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..62e4dcf0f97f54eb3485fe45e8cb02e2722e10e2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/tags/UserTagsActivity.java @@ -0,0 +1,53 @@ +package net.oschina.app.improve.user.tags; + +import android.content.Context; +import android.content.Intent; +import android.view.View; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.user.tags.search.SearchTagsActivity; + +import butterknife.OnClick; + +/** + * 用户标签界面 + * Created by haibin on 2018/05/22. + */ +public class UserTagsActivity extends BackActivity implements View.OnClickListener { + + public static void show(Context context) { + if (!AccountHelper.isLogin()) { + return; + } + context.startActivity(new Intent(context, UserTagsActivity.class)); + } + + + @Override + protected int getContentView() { + return R.layout.activity_user_tags; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + addFragment(R.id.fl_content, UserTagsFragment.newInstance()); + } + + @OnClick({R.id.ll_search, R.id.iv_add}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.ll_search: + SearchTagsActivity.show(this); + break; + case R.id.iv_add: + SearchTagsActivity.show(this); + break; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/tags/UserTagsContract.java b/app/src/main/java/net/oschina/app/improve/user/tags/UserTagsContract.java new file mode 100644 index 0000000000000000000000000000000000000000..7011ef8dde84f4557931aee083eaf0ee25dbcd18 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/tags/UserTagsContract.java @@ -0,0 +1,27 @@ +package net.oschina.app.improve.user.tags; + +import net.oschina.app.improve.base.BaseListPresenter; +import net.oschina.app.improve.base.BaseListView; +import net.oschina.app.improve.base.BasePresenter; +import net.oschina.app.improve.base.BaseView; +import net.oschina.app.improve.bean.Tags; + +/** + * 用户标签界面 + * Created by haibin on 2018/05/22. + */ +interface UserTagsContract { + + interface View extends BaseListView { + void showDeleteSuccess(Tags tags, int position); + + void showDeleteFailure(int strId); + + void showDeleteFailure(String strId); + } + + interface Presenter extends BaseListPresenter { + + void delete(Tags tags, int position); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/tags/UserTagsFragment.java b/app/src/main/java/net/oschina/app/improve/user/tags/UserTagsFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..58321df3d4a81d8f366a3e1c214d9c6b758af045 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/tags/UserTagsFragment.java @@ -0,0 +1,81 @@ +package net.oschina.app.improve.user.tags; + +import android.view.View; + +import com.mcxtzhang.swipemenulib.SwipeMenuLayout; + +import net.oschina.app.improve.base.BaseRecyclerFragment; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Tags; +import net.oschina.app.improve.widget.SimplexToast; + +/** + * 用户标签界面 + * Created by haibin on 2018/05/22. + */ +public class UserTagsFragment extends BaseRecyclerFragment implements UserTagsContract.View { + + + public static UserTagsFragment newInstance() { + return new UserTagsFragment(); + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + ((TagAdapter) mAdapter).setDeleteListener(new BaseRecyclerAdapter.OnViewClickListener() { + @Override + public void onClick(View view, int position) { + if (view.getParent() != null && view.getParent() instanceof SwipeMenuLayout) { + ((SwipeMenuLayout) view.getParent()).smoothClose(); + } + Tags item = mAdapter.getItem(position); + if (item == null) { + return; + } + mPresenter.delete(item, position); + } + }); + new UserTagsPresenter(this); + } + + @Override + protected void onItemClick(Tags tags, int position) { + + } + + + @Override + public void onLoadMore() { + + } + + @Override + public void showDeleteSuccess(Tags tags, int position) { + if (mContext == null) + return; + Tags item = mAdapter.getItem(position); + if (item != null && tags.equals(item)) { + mAdapter.removeItem(position); + } + } + + @Override + public void showDeleteFailure(int strId) { + if (mContext == null) + return; + SimplexToast.show(mContext, strId); + } + + @Override + public void showDeleteFailure(String strId) { + if (mContext == null) + return; + SimplexToast.show(mContext, strId); + } + + @Override + protected BaseRecyclerAdapter getAdapter() { + return new TagAdapter(mContext); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/tags/UserTagsPresenter.java b/app/src/main/java/net/oschina/app/improve/user/tags/UserTagsPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..dc801f5f1322ccb97b59026a51b0800e28779c2c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/tags/UserTagsPresenter.java @@ -0,0 +1,92 @@ +package net.oschina.app.improve.user.tags; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.Tags; +import net.oschina.app.improve.bean.base.ResultBean; + +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * 用户标签界面 + * Created by haibin on 2018/05/22. + */ +class UserTagsPresenter implements UserTagsContract.Presenter { + private final UserTagsContract.View mView; + + UserTagsPresenter(UserTagsContract.View mView) { + this.mView = mView; + this.mView.setPresenter(this); + } + + @Override + public void onRefreshing() { + OSChinaApi.getUserTags(new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> bean = new Gson().fromJson(responseString, + new TypeToken>>() { + }.getType()); + if (bean != null && bean.isSuccess()) { + mView.onRefreshSuccess(bean.getResult()); + } else { + mView.showNetworkError(R.string.state_network_error); + } + } catch (Exception e) { + e.printStackTrace(); + } + mView.onComplete(); + } + }); + } + + @Override + public void onLoadMore() { + + } + + + @Override + public void delete(final Tags tags, final int position) { + OSChinaApi.putUserTags(null, String.valueOf(tags.getId()), + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showDeleteFailure(R.string.delete_failed); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean bean = new Gson().fromJson(responseString, + new TypeToken>() { + }.getType()); + if (bean != null) { + if (bean.getCode() == 1) { + mView.showDeleteSuccess(tags, position); + } else { + mView.showDeleteFailure(bean.getMessage()); + } + } else { + mView.showDeleteFailure(R.string.delete_failed); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/user/tags/search/SearchTagAdapter.java b/app/src/main/java/net/oschina/app/improve/user/tags/search/SearchTagAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..2afb604853358e1d961d96c2e289f1b41cde9f2f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/tags/search/SearchTagAdapter.java @@ -0,0 +1,63 @@ +package net.oschina.app.improve.user.tags.search; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Tags; + + class SearchTagAdapter extends BaseRecyclerAdapter { + + private OnViewClickListener mRelateListener; + + SearchTagAdapter(Context context) { + super(context, ONLY_FOOTER); + } + + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new TagHolder(mInflater.inflate(R.layout.item_list_search_tags, parent, false)); + } + + @Override + protected void onBindClickListener(RecyclerView.ViewHolder holder) { + TagHolder h = (TagHolder) holder; + h.mTextRelate.setTag(holder); + h.mTextRelate.setOnClickListener(mRelateListener); + } + + void setRelateListener(OnViewClickListener mRelateListener) { + this.mRelateListener = mRelateListener; + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Tags item, int position) { + TagHolder h = (TagHolder) holder; + h.mTextTag.setText(item.getName()); + if(item.isRelated()){ + h.mTextRelate.setBackgroundResource(R.drawable.selector_related); + h.mTextRelate.setText("取消关注"); + h.mTextRelate.setTextColor(0xFF6a6a6a); + }else { + h.mTextRelate.setBackgroundResource(R.drawable.selector_event_sign_up); + h.mTextRelate.setText("关注标签"); + h.mTextRelate.setTextColor(0xFFFFFFFF); + } + } + + private static final class TagHolder extends RecyclerView.ViewHolder { + + private TextView mTextTag; + private TextView mTextRelate; + private TagHolder(View itemView) { + super(itemView); + mTextTag = (TextView) itemView.findViewById(R.id.tv_tag); + mTextRelate = (TextView) itemView.findViewById(R.id.tv_relations); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/tags/search/SearchTagsActivity.java b/app/src/main/java/net/oschina/app/improve/user/tags/search/SearchTagsActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..2e0ebe16dd3d5e0124bf06dc43cc1e939fa3eaf3 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/tags/search/SearchTagsActivity.java @@ -0,0 +1,222 @@ +package net.oschina.app.improve.user.tags.search; + +import android.content.Context; +import android.content.Intent; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.SearchView; +import android.util.TypedValue; +import android.view.View; +import android.widget.EditText; + +import net.oschina.app.R; +import net.oschina.app.improve.account.AccountHelper; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.Tags; +import net.oschina.app.improve.widget.RecyclerRefreshLayout; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.util.TDevice; + +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 用户搜索标签界面 + * Created by haibin on 2018/05/28. + */ +public class SearchTagsActivity extends BackActivity implements + View.OnClickListener, + SearchTagsContract.View, + BaseRecyclerAdapter.OnItemClickListener { + + @Bind(R.id.view_searcher) + SearchView mViewSearch; + @Bind(R.id.search_src_text) + EditText mViewSearchEditor; + @Bind(R.id.refreshLayout) + RecyclerRefreshLayout mRefreshLayout; + @Bind(R.id.recyclerView) + RecyclerView mRecyclerView; + + private SearchTagAdapter mAdapter; + private SearchTagsPresenter mPresenter; + + public static void show(Context context) { + if (!AccountHelper.isLogin()) { + return; + } + context.startActivity(new Intent(context, SearchTagsActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_user_tags_search; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mAdapter = new SearchTagAdapter(this); + mAdapter.setOnItemClickListener(this); + mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); + mRecyclerView.setAdapter(mAdapter); + mRefreshLayout.setSuperRefreshLayoutListener(new RecyclerRefreshLayout.SuperRefreshLayoutListener() { + @Override + public void onRefreshing() { + mPresenter.search(mViewSearch.getQuery().toString()); + } + + @Override + public void onLoadMore() { + mPresenter.searchMore(mViewSearch.getQuery().toString()); + mAdapter.setState(BaseRecyclerAdapter.STATE_LOADING, true); + } + + @Override + public void onScrollToBottom() { + // TODO: 2018/1/5 + } + }); + + mViewSearch.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + //mViewSearch.clearFocus(); + TDevice.closeKeyboard(mViewSearchEditor); + mPresenter.search(query); + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + + return false; + } + }); + + mAdapter.setRelateListener(new BaseRecyclerAdapter.OnViewClickListener() { + @Override + public void onClick(View view, int position) { + Tags tags = mAdapter.getItem(position); + if (tags == null) + return; + mPresenter.putTags(tags, position); + } + }); + + mViewSearchEditor.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + } + + @Override + protected void initData() { + super.initData(); + mPresenter = new SearchTagsPresenter(this); + } + + @OnClick({R.id.tv_search}) + @Override + public void onClick(View view) { + if (mPresenter == null) + return; + TDevice.closeKeyboard(mViewSearchEditor); + mPresenter.search(mViewSearchEditor.getText().toString().trim()); + } + + @Override + public void onItemClick(int position, long itemId) { + + } + + @Override + public void showSearchSuccess(List list) { + if (isDestroyed()) { + return; + } + mAdapter.resetItem(list); + } + + @Override + public void showLoadMoreSuccess(List list) { + if (isDestroyed()) { + return; + } + mAdapter.addAll(list); + } + + @Override + public void showSearchFailure(int strId) { + if (isDestroyed()) { + return; + } + SimplexToast.show(this, strId); + } + + @Override + public void showSearchFailure(String str) { + if (isDestroyed()) { + return; + } + SimplexToast.show(this, str); + } + + @Override + public void showNotMore() { + if (isDestroyed()) { + return; + } + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + } + + @Override + public void onComplete() { + if (isDestroyed()) { + return; + } + mRefreshLayout.onComplete(); + } + + + @Override + public void showNetworkError(int strId) { + if (isDestroyed()) { + return; + } + mAdapter.setState(BaseRecyclerAdapter.STATE_NO_MORE, true); + } + + + @Override + public void showPutSuccess(Tags tags, int position) { + if (isDestroyed()) + return; + Tags item = mAdapter.getItem(position); + if (item != null && tags.equals(item)) { + mAdapter.updateItem(position); + } + } + + @Override + public void showPutFailure(int strId) { + if (isDestroyed()) + return; + SimplexToast.show(this, strId); + } + + @Override + public void showPutFailure(String strId) { + if (isDestroyed()) + return; + SimplexToast.show(this, strId); + } + + @Override + public void setPresenter(SearchTagsContract.Presenter presenter) { + + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/user/tags/search/SearchTagsContract.java b/app/src/main/java/net/oschina/app/improve/user/tags/search/SearchTagsContract.java new file mode 100644 index 0000000000000000000000000000000000000000..3c6b9221a5a0f699fcd3ffbb261c602ad93942df --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/tags/search/SearchTagsContract.java @@ -0,0 +1,44 @@ +package net.oschina.app.improve.user.tags.search; + +import net.oschina.app.improve.base.BasePresenter; +import net.oschina.app.improve.base.BaseView; +import net.oschina.app.improve.bean.Tags; + +import java.util.List; + +/** + * 用户搜索标签界面 + * Created by haibin on 2018/05/28. + */ +interface SearchTagsContract { + + interface View extends BaseView { + void showSearchSuccess(List list); + + void showNotMore(); + + void showLoadMoreSuccess(List articles); + + void onComplete(); + + void showSearchFailure(int strId); + + void showSearchFailure(String str); + + + void showPutSuccess(Tags tags, int position); + + void showPutFailure(int strId); + + void showPutFailure(String strId); + } + + interface Presenter extends BasePresenter { + void search(String keyword); + + + void searchMore(String keyword); + + void putTags(Tags tags, int position); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/user/tags/search/SearchTagsPresenter.java b/app/src/main/java/net/oschina/app/improve/user/tags/search/SearchTagsPresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..1298f84b06c7b3d2429e6fd058372d6edefe5bd8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/user/tags/search/SearchTagsPresenter.java @@ -0,0 +1,160 @@ +package net.oschina.app.improve.user.tags.search; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.Tags; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; + +import java.lang.reflect.Type; + +import cz.msebera.android.httpclient.Header; + +/** + * 用户搜索标签界面 + * Created by haibin on 2018/05/28. + */ +class SearchTagsPresenter implements SearchTagsContract.Presenter { + private final SearchTagsContract.View mView; + private String mToken; + private String mKeyword; + + SearchTagsPresenter(SearchTagsContract.View mView) { + this.mView = mView; + this.mView.setPresenter(this); + } + + @Override + public void search(String keyword) { + if (TextUtils.isEmpty(keyword)) { + mView.showSearchFailure(R.string.search_keyword_empty_error); + mView.onComplete(); + return; + } + mKeyword = keyword; + OSChinaApi.searchUserTags(keyword, null, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showSearchFailure(R.string.network_timeout_hint); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> bean = new Gson().fromJson(responseString, getType()); + if (bean != null) { + if (bean.isSuccess() && bean.getResult() != null) { + PageBean result = bean.getResult(); + mToken = result.getNextPageToken(); + mView.showSearchSuccess(result.getItems()); + if ((result.getItems() == null || + result.getItems().size() == 0)) { + mView.showNotMore(); + } + } else { + mView.showSearchFailure(bean.getMessage()); + } + } else { + mView.showSearchFailure(R.string.search_error); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.onComplete(); + } + } + }); + } + + @Override + public void searchMore(String keyword) { + if (TextUtils.isEmpty(mKeyword)) { + mView.showSearchFailure(R.string.search_keyword_empty_error); + return; + } + OSChinaApi.searchUserTags(keyword, mToken, new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showSearchFailure(R.string.network_timeout_hint); + mView.onComplete(); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean> bean = new Gson().fromJson(responseString, getType()); + if (bean != null) { + if (bean.isSuccess() && bean.getResult() != null) { + PageBean result = bean.getResult(); + mToken = result.getNextPageToken(); + mView.showLoadMoreSuccess(result.getItems()); + if ((result.getItems() == null || + result.getItems().size() == 0)) { + mView.showNotMore(); + } + } else { + mView.showSearchFailure(bean.getMessage()); + } + } else { + mView.showSearchFailure(R.string.search_error); + } + mView.onComplete(); + } catch (Exception e) { + e.printStackTrace(); + mView.onComplete(); + } + } + }); + } + + @Override + public void putTags(final Tags tags, final int position) { + String ids = null; + String deleteIds = null; + if (tags.isRelated()) { + deleteIds = String.valueOf(tags.getId()); + } else { + ids = String.valueOf(tags.getId()); + } + OSChinaApi.putUserTags(ids, deleteIds, + new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showPutFailure(R.string.delete_failed); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + ResultBean bean = new Gson().fromJson(responseString, + new TypeToken>() { + }.getType()); + if (bean != null) { + if (bean.getCode() == 1) { + tags.setRelated(!tags.isRelated()); + mView.showPutSuccess(tags, position); + } else { + mView.showPutFailure(bean.getMessage()); + } + } else { + mView.showPutFailure(R.string.delete_failed); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + private static Type getType() { + return new TypeToken>>() { + }.getType(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/AES.java b/app/src/main/java/net/oschina/app/improve/utils/AES.java new file mode 100644 index 0000000000000000000000000000000000000000..ce02e6c3ab3444066045332f1b9a1ec83a2d0b32 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/AES.java @@ -0,0 +1,81 @@ +package net.oschina.app.improve.utils; + +import android.text.TextUtils; +import android.util.Base64; + +import net.oschina.app.BuildConfig; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * 数据加密类 + * Created by haibin on 2017/5/8. + */ +@SuppressWarnings("unused") +public final class AES { + private static String AES_KEY = BuildConfig.AES_KEY; + private static String AES_IV = BuildConfig.AES_IV; + + private static final String AES_MODE = "AES/CBC/PKCS7Padding"; + + public static String encryptByBase64(String content) { + try { + SecretKeySpec keysSpec = new SecretKeySpec(AES_KEY.getBytes(), "AES"); + final Cipher cipher = Cipher.getInstance(AES_MODE); + IvParameterSpec ivSpec = new IvParameterSpec(AES.AES_IV.getBytes()); + cipher.init(Cipher.ENCRYPT_MODE, keysSpec, ivSpec); + byte[] cipherText = cipher.doFinal(content.getBytes()); + return Base64.encodeToString(cipherText, Base64.DEFAULT); + }catch (Exception e){ + e.printStackTrace(); + return ""; + } + } + + + public static String encryptByBase64(String content,String key,String iv) { + try { + SecretKeySpec keysSpec = new SecretKeySpec(key.getBytes(), "AES"); + final Cipher cipher = Cipher.getInstance(AES_MODE); + IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes()); + cipher.init(Cipher.ENCRYPT_MODE, keysSpec, ivSpec); + byte[] cipherText = cipher.doFinal(content.getBytes()); + return Base64.encodeToString(cipherText, Base64.DEFAULT); + }catch (Exception e){ + e.printStackTrace(); + return ""; + } + } + + + /* + * 解密 + */ + public static String decryptByBase64( String content) { + if (TextUtils.isEmpty(content)) { + return content; + } + try { + byte[] enc = Base64.decode(content,Base64.DEFAULT); + byte[] result = decrypt(enc); + return new String(result); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /* + * 解密 + */ + private static byte[] decrypt(byte[] encrypted) throws Exception { + byte[] raw = AES_KEY.getBytes(); + SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); + Cipher cipher = Cipher.getInstance(AES_MODE); + cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(AES.AES_IV.getBytes())); + return cipher.doFinal(encrypted); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/BitmapUtil.java b/app/src/main/java/net/oschina/app/improve/utils/BitmapUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..9ce8b7122ac4d84845c46ea04dc2907b98bf87d3 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/BitmapUtil.java @@ -0,0 +1,395 @@ +package net.oschina.app.improve.utils; + +import android.annotation.TargetApi; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.media.ExifInterface; +import android.os.Build; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import static net.oschina.common.utils.StreamUtil.close; + +/** + * Bitmap 工具类 + * + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ +public final class BitmapUtil { + /** + * A default size to use to increase hit rates when the required size isn't defined. + * Currently 64KB. + */ + private final static int DEFAULT_BUFFER_SIZE = 64 * 1024; + + /** + * 创建一个图片处理Options + * + * @return {@link android.graphics.BitmapFactory.Options} + */ + static BitmapFactory.Options createOptions() { + return new BitmapFactory.Options(); + } + + /** + * 把一个{@link android.graphics.BitmapFactory.Options}进行参数复原操作, + * 避免重复创建新的 {@link android.graphics.BitmapFactory.Options} + * + * @param options {@link android.graphics.BitmapFactory.Options} + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + private static void resetOptions(BitmapFactory.Options options) { + options.inTempStorage = null; + options.inDither = false; + options.inScaled = false; + options.inSampleSize = 1; + options.inPreferredConfig = null; + options.inJustDecodeBounds = false; + options.inDensity = 0; + options.inTargetDensity = 0; + options.outWidth = 0; + options.outHeight = 0; + options.outMimeType = null; + + if (Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT) { + options.inBitmap = null; + options.inMutable = true; + } + } + + /** + * 获取图片的真实后缀 + * + * @param filePath 图片存储地址 + * @return 图片类型后缀 + */ + public static String getExtension(String filePath) { + BitmapFactory.Options options = createOptions(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(filePath, options); + String mimeType = options.outMimeType; + return mimeType.substring(mimeType.lastIndexOf("/") + 1); + } + + private static Bitmap decodeBitmap(final File file, + final int maxWidth, + final int maxHeight, + byte[] byteStorage, + BitmapFactory.Options options, + boolean exactDecode) { + InputStream is; + try { + // In this, we can set the buffer size + is = new BufferedInputStream(new FileInputStream(file), + byteStorage == null ? DEFAULT_BUFFER_SIZE : byteStorage.length); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } + + if (options == null) + options = createOptions(); + else + resetOptions(options); + + // First decode with inJustDecodeBounds=true to check dimensions + options.inJustDecodeBounds = true; + + // 5MB. This is the max image header size we can handle, we preallocate a much smaller buffer + // but will resize up to this amount if necessary. + is.mark(5 * 1024 * 1024); + BitmapFactory.decodeStream(is, null, options); + + // Reset the inputStream + try { + is.reset(); + } catch (IOException e) { + e.printStackTrace(); + close(is); + resetOptions(options); + return null; + } + + // Calculate inSampleSize + calculateScaling(options, maxWidth, maxHeight, exactDecode); + + // Init the BitmapFactory.Options.inTempStorage value + if (byteStorage == null) + byteStorage = new byte[DEFAULT_BUFFER_SIZE]; + options.inTempStorage = byteStorage; + + // Decode bitmap with inSampleSize set FALSE + options.inJustDecodeBounds = false; + Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); + + // Close the Stream + close(is); + // And Reset the option + resetOptions(options); + + // To scale bitmap to user set + bitmap = scaleBitmap(bitmap, maxWidth, maxHeight, true); + + return bitmap; + + } + + /** + * 按长宽比缩小一个Bitmap + * + * @param source 待缩小的{@link Bitmap} + * @param scale 缩放比0~1,1代表不缩放 + * @param recycleSource 是否释放Bitmap源 + * @return 一个缩小后的Bitmap + */ + private static Bitmap scaleBitmap(Bitmap source, float scale, boolean recycleSource,int angle) { + if (scale <= 0 || scale >= 1) + return source; + Matrix m = new Matrix(); + m.postRotate(angle); + final int width = source.getWidth(); + final int height = source.getHeight(); + m.setScale(scale, scale); + Bitmap scaledBitmap = Bitmap.createBitmap(source, 0, 0, width, height, m, false); + if (recycleSource) + source.recycle(); + return scaledBitmap; + } + + /** + * 按照长宽比缩小一个Bitmap到指定尺寸, + * 当传入的高宽都大于原始值时将不做缩小操作 + * + * @param source 待缩小的{@link Bitmap} + * @param targetMaxWidth 目标宽度 + * @param targetMaxHeight 目标高度 + * @param recycleSource 是否释放Bitmap源 + * @return 一个缩小后的Bitmap + */ + static Bitmap scaleBitmap(Bitmap source, int targetMaxWidth, int targetMaxHeight, boolean recycleSource) { + int sourceWidth = source.getWidth(); + int sourceHeight = source.getHeight(); + + Bitmap scaledBitmap = source; + if (sourceWidth > targetMaxWidth || sourceHeight > targetMaxHeight) { + float minScale = Math.min(targetMaxWidth / (float) sourceWidth, + targetMaxHeight / (float) sourceHeight); + scaledBitmap = Bitmap.createScaledBitmap(scaledBitmap, + (int) (sourceWidth * minScale), + (int) (sourceHeight * minScale), false); + if (recycleSource) + source.recycle(); + } + + return scaledBitmap; + } + + /** + * 通过{@link android.graphics.BitmapFactory.Options}计算图片的缩放比, + * 并将缩放后的信息存储在传入的{@link android.graphics.BitmapFactory.Options}中, + * 以便后续的 {@link BitmapFactory#decodeStream(InputStream)}等操作 + * + * @param options 一个图片的{@link android.graphics.BitmapFactory.Options}, 含有图片的基础信息 + * @param requestedMaxWidth 目标宽度 + * @param requestedMaxHeight 目标高度 + * @param exactDecode 是否精确计算,该参数只在 {@link android.os.Build.VERSION#SDK_INT} 大于 Api19 时有效 + */ + private static BitmapFactory.Options calculateScaling(BitmapFactory.Options options, + final int requestedMaxWidth, + final int requestedMaxHeight, + boolean exactDecode) { + int sourceWidth = options.outWidth; + int sourceHeight = options.outHeight; + + if (sourceWidth <= requestedMaxWidth && sourceHeight <= requestedMaxHeight) { + return options; + } + + final float maxFloatFactor = Math.max(sourceHeight / (float) requestedMaxHeight, + sourceWidth / (float) requestedMaxWidth); + final int maxIntegerFactor = (int) Math.floor(maxFloatFactor); + final int lesserOrEqualSampleSize = Math.max(1, Integer.highestOneBit(maxIntegerFactor)); + + options.inSampleSize = lesserOrEqualSampleSize; + // Density scaling is only supported if inBitmap is null prior to KitKat. Avoid setting + // densities here so we calculate the final Bitmap size correctly. + if (exactDecode && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + float scaleSize = sourceWidth / (float) lesserOrEqualSampleSize; + float outSize = sourceWidth / maxFloatFactor; + + options.inTargetDensity = 1000; + options.inDensity = (int) (1000 * (scaleSize / outSize) + 0.5); + + // If isScaling + if (options.inTargetDensity != options.inDensity) { + options.inScaled = true; + } else { + options.inDensity = options.inTargetDensity = 0; + } + } + return options; + } + + public final static class Compressor { + + public static File compressImage(final File sourceFile, final long maxSize, + final int minQuality, final int maxWidth, + final int maxHeight) { + return compressImage(sourceFile, maxSize, minQuality, maxWidth, maxHeight, true); + } + + static File compressImage(final File sourceFile, final long maxSize, + final int minQuality, final int maxWidth, + final int maxHeight, boolean exactDecode) { + return compressImage(sourceFile, maxSize, minQuality, maxWidth, maxHeight, null, null, exactDecode); + } + + /** + * 压缩图片 + * + * @param sourceFile 原图地址 + * @param maxSize 最大文件地址byte + * @param minQuality 最小质量 + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @param byteStorage 用于批量压缩时的buffer,不必要为null, + * 需要时,推荐 {{@link #DEFAULT_BUFFER_SIZE}} + * @param options 批量压缩时复用参数,可调用 {{@link #createOptions()}} 得到 + * @param exactDecode 是否精确解码, TRUE: 在4.4及其以上机器中能更节约内存 + * @return 返回压缩后的图片文件,该图片存储在原图同级目录下,以compress.temp结尾 + */ + static File compressImage(final File sourceFile, + final long maxSize, + final int minQuality, + final int maxWidth, + final int maxHeight, + byte[] byteStorage, + BitmapFactory.Options options, + boolean exactDecode) { + // build source file + if (sourceFile == null || !sourceFile.exists() || !sourceFile.canRead()) + return null; + + // create new temp file + final File tempFile = new File(sourceFile.getParent(), + String.format("compress_%s.temp", System.currentTimeMillis())); + + if (!tempFile.exists()) { + try { + if (!tempFile.createNewFile()) + return null; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + // build to bitmap + Bitmap bitmap = decodeBitmap(sourceFile, maxWidth, maxHeight, byteStorage, options, exactDecode); + if (bitmap == null) + return null; + int angle = readPictureDegree(sourceFile.getPath()); + if (angle != 0) { + bitmap = rotateBitmap(angle, bitmap); + } + if (bitmap == null) + return null; + // Get the bitmap format + Bitmap.CompressFormat compressFormat = bitmap.hasAlpha() ? + Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG; + + // Write to out put file + boolean isOk = false; + for (int i = 1; i <= 10; i++) { + // In this we change the quality start 92% + int quality = 92; + for (; ; ) { + BufferedOutputStream outputStream = null; + try { + outputStream = new BufferedOutputStream(new FileOutputStream(tempFile)); + bitmap.compress(compressFormat, quality, outputStream); + } catch (IOException e) { + e.printStackTrace(); + // on IOException we need recycle the bitmap + bitmap.recycle(); + return null; + } finally { + close(outputStream); + } + // Check file size + long outSize = tempFile.length(); + if (outSize <= maxSize) { + isOk = true; + break; + } + if (quality < minQuality) + break; + quality--; + } + + if (isOk) { + break; + } else { + // If not ok, we need scale the Bitmap to small + // In this, once subtract 2%, most 20% + bitmap = scaleBitmap(bitmap, 1 - (0.2f * i), true,angle); + } + } + // recycle bitmap + bitmap.recycle(); + + // The end, If not success, return false + if (!isOk) + return null; + + // Rename to out file + return tempFile; + } + } + + /** + * 获取图片的旋转角度 + * + * @param path path + * @return 图片的旋转角度 + */ + public static int readPictureDegree(String path) { + int degree = 0; + try { + ExifInterface exifInterface = new ExifInterface(path); + int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_90: + degree = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + degree = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + degree = 270; + break; + } + } catch (IOException e) { + e.printStackTrace(); + } + return degree; + } + + private static Bitmap rotateBitmap(int angle, Bitmap bitmap) { + Matrix matrix = new Matrix(); + matrix.postRotate(angle); + Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, + bitmap.getWidth(), bitmap.getHeight(), matrix, true); + bitmap.recycle(); + return resizedBitmap; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/CacheManager.java b/app/src/main/java/net/oschina/app/improve/utils/CacheManager.java new file mode 100644 index 0000000000000000000000000000000000000000..3d455a7534336ad5b01f59d07de40ada05b9e33f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/CacheManager.java @@ -0,0 +1,208 @@ +package net.oschina.app.improve.utils; + +import android.content.Context; +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.internal.$Gson$Types; + +import net.oschina.app.improve.app.AppOperator; +import net.oschina.common.utils.StreamUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.Reader; +import java.io.Serializable; +import java.io.Writer; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; + +import static com.google.gson.internal.$Gson$Preconditions.checkArgument; +import static com.google.gson.internal.$Gson$Preconditions.checkNotNull; +import static com.google.gson.internal.$Gson$Types.canonicalize; +import static com.google.gson.internal.$Gson$Types.typeToString; + +/** + * Created by haibin + * on 2016/11/7. + * change by fei + * on 2016/12/9 + */ + +public final class CacheManager { + + public static boolean saveToJson(Context context, String fileName, List list) { + if (context == null || list == null) + return false; + String path = context.getCacheDir() + "/" + fileName + ".json"; + File file = new File(path); + if (list.size() == 0) { + return !file.exists() || file.delete(); + } + + try { + return !(!file.exists() && !file.createNewFile()) + && save(file, list); + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + + /** + * save cache file + * + * @param file file + * @param list list + */ + private static boolean save(File file, List list) { + Writer writer = null; + try { + writer = new FileWriter(file); + AppOperator.getGson().toJson(list, writer); + return true; + } catch (Exception e) { + e.printStackTrace(); + } finally { + StreamUtil.close(writer); + } + return false; + } + + /** + * 移除缓存 + */ + @SuppressWarnings("all") + public static void removeCahche(Context context, String fileName) { + String path = context.getCacheDir() + "/" + fileName; + File file = new File(path); + if (file.exists()) { + file.delete(); + } + } + + public static boolean saveToJson(Context context, String fileName, Object object) { + String json = new Gson().toJson(object); + String path = context.getCacheDir() + "/" + fileName; + File file = new File(path); + FileOutputStream os = null; + try { + if (!file.exists() && !file.createNewFile()) + return false; + os = new FileOutputStream(file); + os.write(json.getBytes("utf-8")); + return true; + } catch (Exception e) { + e.printStackTrace(); + } finally { + StreamUtil.close(os); + } + return false; + } + + public static T readFromJson(Context context, String fileName, Class cla) { + return readJson(context, fileName, cla); + } + + public static T readListJson(Context context, String fileName, Class clx) { + if (clx == null || TextUtils.isEmpty(fileName)) return null; + Type type = getListType(clx); + return readJson(context, fileName, type); + } + + @SuppressWarnings("all") + public static T readJson(Context context, String fileName, Type clx) { + if (clx == null || context == null) return null; + String path = context.getCacheDir() + "/" + fileName + ".json"; + File file = new File(path); + if (!file.exists()) + return null; + Reader reader = null; + try { + reader = new FileReader(file); + return AppOperator.getGson().fromJson(reader, clx); + } catch (Exception e) { + e.printStackTrace(); + } finally { + StreamUtil.close(reader); + } + return null; + } + + private static Type getListType(Class clx) { + return $Gson$Types.canonicalize((new ParameterizedTypeImpl(null, List.class, clx))); + } + + private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable { + private final Type ownerType; + private final Type rawType; + private final Type[] typeArguments; + + public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) { + // require an owner type if the raw type needs it + if (rawType instanceof Class) { + Class rawTypeAsClass = (Class) rawType; + boolean isStaticOrTopLevelClass = Modifier.isStatic(rawTypeAsClass.getModifiers()) + || rawTypeAsClass.getEnclosingClass() == null; + checkArgument(ownerType != null || isStaticOrTopLevelClass); + } + + this.ownerType = ownerType == null ? null : canonicalize(ownerType); + this.rawType = canonicalize(rawType); + this.typeArguments = typeArguments.clone(); + for (int t = 0; t < this.typeArguments.length; t++) { + checkNotNull(this.typeArguments[t]); + this.typeArguments[t] = canonicalize(this.typeArguments[t]); + } + } + + public Type[] getActualTypeArguments() { + return typeArguments.clone(); + } + + public Type getRawType() { + return rawType; + } + + public Type getOwnerType() { + return ownerType; + } + + @Override + public boolean equals(Object other) { + return other instanceof ParameterizedType + && $Gson$Types.equals(this, (ParameterizedType) other); + } + + @Override + public int hashCode() { + return Arrays.hashCode(typeArguments) + ^ rawType.hashCode() + ^ (ownerType != null ? ownerType.hashCode() : 0); + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(30 * (typeArguments.length + 1)); + stringBuilder.append(typeToString(rawType)); + + if (typeArguments.length == 0) { + return stringBuilder.toString(); + } + + stringBuilder.append("<").append(typeToString(typeArguments[0])); + for (int i = 1; i < typeArguments.length; i++) { + stringBuilder.append(", ").append(typeToString(typeArguments[i])); + } + return stringBuilder.append(">").toString(); + } + + private static final long serialVersionUID = 0; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/DialogHelper.java b/app/src/main/java/net/oschina/app/improve/utils/DialogHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..54e9c891a0c36d6a483b794883187d971070d3fe --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/DialogHelper.java @@ -0,0 +1,426 @@ +package net.oschina.app.improve.utils; + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.AppCompatEditText; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.comment.adapter.CommentItemAdapter; +import net.oschina.app.improve.media.Util; + +import static android.view.View.OVER_SCROLL_NEVER; + +/** + * 通用的对话框 + * Created by haibin + * on 2016/11/2. + */ +@SuppressWarnings("all") +public final class DialogHelper { + public static AlertDialog.Builder getDialog(Context context) { + return new AlertDialog.Builder(context, R.style.App_Theme_Dialog_Alert); + } + + /** + * 获取一个普通的消息对话框,没有取消按钮 + */ + public static AlertDialog.Builder getMessageDialog( + Context context, + String title, + String message, + boolean cancelable) { + return getDialog(context) + .setCancelable(cancelable) + .setTitle(title) + .setMessage(message) + .setPositiveButton("确定", null); + } + + /** + * 获取一个普通的消息对话框,没有取消按钮 + */ + public static AlertDialog.Builder getMessageDialog( + Context context, + String title, + String message) { + return getMessageDialog(context, title, message, true); + } + + /** + * 获取一个普通的消息对话框,没有取消按钮 + */ + public static AlertDialog.Builder getMessageDialog(Context context, String message) { + return getMessageDialog(context, "", message, true); + } + + /** + * 获取一个普通的消息对话框,没有取消按钮 + */ + public static AlertDialog.Builder getMessageDialog( + Context context, + String title, + String message, + String positiveText) { + return getDialog(context) + .setCancelable(true) + .setTitle(title) + .setMessage(message) + .setPositiveButton(positiveText, null); + } + + public static AlertDialog.Builder getConfirmDialog(Context context, + String title, + View view, + DialogInterface.OnClickListener positiveListener) { + return getDialog(context) + .setTitle(title) + .setView(view) + .setPositiveButton("确定", positiveListener) + .setNegativeButton("取消", null); + } + + public static AlertDialog.Builder getConfirmDialog(Context context, + String title, + View view, + String positiveText, + String negativeText, + DialogInterface.OnClickListener positiveListener) { + return getDialog(context) + .setTitle(title) + .setView(view) + .setCancelable(true) + .setPositiveButton(positiveText, positiveListener) + .setNegativeButton(negativeText, null); + } + + /** + * 获取一个验证对话框 + */ + public static AlertDialog.Builder getConfirmDialog( + Context context, + String title, + String message, + String positiveText, + String negativeText, + boolean cancelable, + DialogInterface.OnClickListener positiveListener, + DialogInterface.OnClickListener negativeListener) { + return getDialog(context) + .setCancelable(cancelable) + .setTitle(title) + .setMessage(message) + .setPositiveButton(positiveText, positiveListener) + .setNegativeButton(negativeText, negativeListener); + } + + /** + * 获取一个验证对话框 + */ + public static AlertDialog.Builder getConfirmDialog( + Context context, String message, + DialogInterface.OnClickListener positiveListener, + DialogInterface.OnClickListener negativeListener) { + return getDialog(context) + .setMessage(message) + .setPositiveButton("确定", positiveListener) + .setNegativeButton("取消", negativeListener); + } + + public static AlertDialog.Builder getSingleChoiceDialog( + Context context, + String title, + String[] arrays, + int selectIndex, + DialogInterface.OnClickListener onClickListener) { + AlertDialog.Builder builder = getDialog(context); + builder.setSingleChoiceItems(arrays, selectIndex, onClickListener); + if (!TextUtils.isEmpty(title)) { + builder.setTitle(title); + } + builder.setNegativeButton("取消", null); + return builder; + } + + + /** + * 获取一个验证对话框,没有点击事件 + */ + public static AlertDialog.Builder getConfirmDialog( + Context context, + String title, + String message, + String positiveText, + String negativeText, + boolean cancelable, + DialogInterface.OnClickListener positiveListener) { + return getConfirmDialog( + context, title, message, positiveText, + negativeText, cancelable, positiveListener, null); + } + + /** + * 获取一个验证对话框,没有点击事件 + */ + public static AlertDialog.Builder getConfirmDialog( + Context context, + String title, + String message, + String positiveText, + String negativeText, + DialogInterface.OnClickListener positiveListener) { + return getConfirmDialog( + context, title, message, positiveText, negativeText, true, positiveListener, null); + } + + + /** + * 获取一个验证对话框,没有点击事件 + */ + public static AlertDialog.Builder getConfirmDialog( + Context context, + String title, + String message, + String positiveText, + String negativeText, + boolean cancelable) { + return getConfirmDialog( + context, title, message, positiveText, negativeText, cancelable, null, null); + } + + /** + * 获取一个验证对话框,没有点击事件 + */ + public static AlertDialog.Builder getConfirmDialog( + Context context, + String message, + String positiveText, + String negativeText, + boolean cancelable) { + return getConfirmDialog(context, "", message, positiveText, negativeText + , cancelable, null, null); + } + + /** + * 获取一个验证对话框,没有点击事件,取消、确定 + */ + public static AlertDialog.Builder getConfirmDialog( + Context context, + String title, + String message, + boolean cancelable) { + return getConfirmDialog(context, title, message, "确定", "取消", cancelable, null, null); + } + + /** + * 获取一个验证对话框,没有点击事件,取消、确定 + */ + public static AlertDialog.Builder getConfirmDialog( + Context context, + String message, + boolean cancelable, + DialogInterface.OnClickListener positiveListener) { + return getConfirmDialog(context, "", message, "确定", "取消", cancelable, positiveListener, null); + } + + /** + * 获取一个验证对话框,没有点击事件,取消、确定 + */ + public static AlertDialog.Builder getConfirmDialog( + Context context, + String message, + DialogInterface.OnClickListener positiveListener) { + return getConfirmDialog(context, "", message, "确定", "取消", positiveListener); + } + + /** + * 获取一个验证对话框,没有点击事件,取消、确定 + */ + public static AlertDialog.Builder getConfirmDialog( + Context context, + String title, + String message) { + return getConfirmDialog(context, title, message, "确定", "取消", false, null, null); + } + + /** + * 获取一个输入对话框 + */ + public static AlertDialog.Builder getInputDialog( + Context context, + String title, + AppCompatEditText editText, + String positiveText, + String negativeText, + boolean cancelable, + DialogInterface.OnClickListener positiveListener, + DialogInterface.OnClickListener negativeListener) { + return getDialog(context) + .setCancelable(cancelable) + .setTitle(title) + .setView(editText) + .setPositiveButton(positiveText, positiveListener) + .setNegativeButton(negativeText, negativeListener); + } + + /** + * 获取一个输入对话框 + */ + public static AlertDialog.Builder getInputDialog( + Context context, String title, + AppCompatEditText editText, + String positiveText, + String negativeText, + boolean cancelable, + DialogInterface.OnClickListener positiveListener) { + return getInputDialog( + context, + title, + editText, + positiveText, + negativeText, + cancelable, + positiveListener, + null); + } + + /** + * 获取一个输入对话框 + */ + public static AlertDialog.Builder getInputDialog( + Context context, + String title, + AppCompatEditText editText, + boolean cancelable, + DialogInterface.OnClickListener positiveListener) { + return getInputDialog(context, title, editText, "确定", "取消" + , cancelable, positiveListener, null); + } + + /** + * 获取一个输入对话框 + */ + public static AlertDialog.Builder getInputDialog( + Context context, String title, AppCompatEditText editText, String positiveText, + boolean cancelable, + DialogInterface.OnClickListener positiveListener, + DialogInterface.OnClickListener negativeListener) { + return getInputDialog( + context, title, editText, positiveText, "取消", cancelable + , positiveListener, negativeListener); + } + + /** + * 获取一个输入对话框 + */ + public static AlertDialog.Builder getInputDialog( + Context context, String title, AppCompatEditText editText, + boolean cancelable, + DialogInterface.OnClickListener positiveListener, + DialogInterface.OnClickListener negativeListener) { + return getInputDialog( + context, title, editText, "确定", "取消", cancelable + , positiveListener, negativeListener); + } + + + /** + * 获取一个等待对话框 + */ + public static ProgressDialog getProgressDialog(Context context) { + return new ProgressDialog(context); + } + + /** + * 获取一个等待对话框 + */ + public static ProgressDialog getProgressDialog(Context context, boolean cancelable) { + ProgressDialog dialog = getProgressDialog(context); + dialog.setCancelable(cancelable); + return dialog; + } + + /** + * 获取一个等待对话框 + */ + public static ProgressDialog getProgressDialog(Context context, String message) { + ProgressDialog dialog = getProgressDialog(context); + dialog.setMessage(message); + return dialog; + } + + /** + * 获取一个等待对话框 + */ + public static ProgressDialog getProgressDialog( + Context context, String title, String message, boolean cancelable) { + ProgressDialog dialog = getProgressDialog(context); + dialog.setCancelable(cancelable); + dialog.setTitle(title); + dialog.setMessage(message); + return dialog; + } + + /** + * 获取一个等待对话框 + */ + public static ProgressDialog getProgressDialog( + Context context, String message, boolean cancelable) { + ProgressDialog dialog = getProgressDialog(context); + dialog.setCancelable(cancelable); + dialog.setMessage(message); + return dialog; + } + + public static AlertDialog.Builder getSelectDialog( + Context context, String title, String[] items, + String positiveText, + DialogInterface.OnClickListener itemListener) { + return getDialog(context) + .setTitle(title) + .setItems(items, itemListener) + .setPositiveButton(positiveText, null); + + } + + public static AlertDialog.Builder getSelectDialog( + Context context, String[] items, + String positiveText, + DialogInterface.OnClickListener itemListener) { + return getDialog(context) + .setItems(items, itemListener) + .setPositiveButton(positiveText, null); + + } + + public static AlertDialog.Builder getSelectDialog(Context context, View view, String positiveText, + DialogInterface.OnClickListener itemListener) { + return getDialog(context) + .setView(view) + .setPositiveButton(positiveText, null); + } + + public static AlertDialog.Builder getRecyclerViewDialog(Context context, BaseRecyclerAdapter.OnItemClickListener listener) { + RecyclerView recyclerView = new RecyclerView(context); + RecyclerView.LayoutParams params = + new GridLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + recyclerView.setPadding(Util.dipTopx(context, 16), Util.dipTopx(context, 16), + Util.dipTopx(context, 16), Util.dipTopx(context, 16)); + recyclerView.setLayoutParams(params); + recyclerView.setLayoutManager(new GridLayoutManager(context, 3)); + CommentItemAdapter adapter = new CommentItemAdapter(context); + adapter.setOnItemClickListener(listener); + recyclerView.setAdapter(adapter); + recyclerView.setOverScrollMode(OVER_SCROLL_NEVER); + return getDialog(context) + .setView(recyclerView) + .setPositiveButton(null, null); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/ListenAccountChangeReceiver.java b/app/src/main/java/net/oschina/app/improve/utils/ListenAccountChangeReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..26dd6e4948b20f2b1322559f560c919190d0c8a4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/ListenAccountChangeReceiver.java @@ -0,0 +1,48 @@ +package net.oschina.app.improve.utils; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import net.oschina.app.bean.Constants; +import net.oschina.app.util.TLog; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ + +public class ListenAccountChangeReceiver extends BroadcastReceiver { + public static final String TAG = ListenAccountChangeReceiver.class.getSimpleName(); + private Service service; + + private ListenAccountChangeReceiver(Service service) { + this.service = service; + } + + public static ListenAccountChangeReceiver start(Service service) { + TLog.d(TAG, "start: " + service); + ListenAccountChangeReceiver receiveBroadCast = new ListenAccountChangeReceiver(service); + IntentFilter filter = new IntentFilter(); + filter.addAction(Constants.INTENT_ACTION_LOGOUT); + service.registerReceiver(receiveBroadCast, filter); + return receiveBroadCast; + } + + @Override + public void onReceive(Context context, Intent intent) { + TLog.d(TAG, "onReceive: " + service); + if (service != null) + service.stopSelf(); + } + + public void destroy() { + TLog.d(TAG, "destroy: " + service); + if (service != null) { + service.unregisterReceiver(this); + service = null; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/MD5.java b/app/src/main/java/net/oschina/app/improve/utils/MD5.java new file mode 100644 index 0000000000000000000000000000000000000000..1f7addea819ef102a8728f3e45611288e7b6f570 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/MD5.java @@ -0,0 +1,143 @@ +package net.oschina.app.improve.utils; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * 数据加密类 + * Created by haibin on 2017/5/8. + */ +@SuppressWarnings("unused") +public final class MD5 { + + public static String getMD5(String instr) { + String s = null; + char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f'}; + try { + MessageDigest md = MessageDigest + .getInstance("MD5"); + md.update(instr.getBytes()); + byte tmp[] = md.digest(); + char str[] = new char[16 * 2]; + int k = 0; + for (int i = 0; i < 16; i++) { + + byte byte0 = tmp[i]; + str[k++] = hexDigits[byte0 >>> 4 & 0xf]; + + str[k++] = hexDigits[byte0 & 0xf]; + } + s = new String(str); + + } catch (Exception e) { + e.printStackTrace(); + } + return s; + } + + public static String md5(String string) { + byte[] hash; + try { + hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8")); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Huh, MD5 should be supported?", e); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Huh, UTF-8 should be supported?", e); + } + + StringBuilder hex = new StringBuilder(hash.length * 2); + for (byte b : hash) { + if ((b & 0xFF) < 0x10) hex.append("0"); + hex.append(Integer.toHexString(b & 0xFF)); + } + return hex.toString(); + } + + public static String get32MD5Str(String str) { + MessageDigest messageDigest = null; + try { + messageDigest = MessageDigest.getInstance("MD5"); + messageDigest.reset(); + messageDigest.update(str.getBytes("UTF-8")); + } catch (NoSuchAlgorithmException e) { + System.exit(-1); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + byte[] byteArray = messageDigest.digest(); + StringBuilder md5StrBuff = new StringBuilder(); + for (byte aByteArray : byteArray) { + if (Integer.toHexString(0xFF & aByteArray).length() == 1) + md5StrBuff.append("0").append(Integer.toHexString(0xFF & aByteArray)); + else + md5StrBuff.append(Integer.toHexString(0xFF & aByteArray)); + } + return md5StrBuff.toString(); + } + + public static int getCRC32(String str) { + int[] table = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, + }; + byte[] bytes = str.getBytes(); + int crc = 0xffffffff; + for (byte b : bytes) { + crc = (crc >>> 8 ^ table[(crc ^ b) & 0xff]); + } + crc ^= 0xffffffff; + return crc; + } + + public static String getMessageDigest(byte[] buffer) { + char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + try { + MessageDigest mdTemp = MessageDigest.getInstance("MD5"); + mdTemp.update(buffer); + byte[] md = mdTemp.digest(); + int j = md.length; + char str[] = new char[j * 2]; + int k = 0; + for (byte byte0 : md) { + str[k++] = hexDigits[byte0 >>> 4 & 0xf]; + str[k++] = hexDigits[byte0 & 0xf]; + } + return new String(str); + } catch (Exception e) { + return null; + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/NetworkUtil.java b/app/src/main/java/net/oschina/app/improve/utils/NetworkUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..80cb9fc6e95c2370db84b907ccc975c5c78fbe56 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/NetworkUtil.java @@ -0,0 +1,59 @@ +package net.oschina.app.improve.utils; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.telephony.TelephonyManager; + +/** + * 网络管理 + * Created by huanghaibin on 2017/11/6. + */ + +public final class NetworkUtil { + @SuppressWarnings("deprecation") + public static String getNetwork(Context context) { + ConnectivityManager connect = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connect == null) + return "null"; + NetworkInfo activeNetInfo = connect.getActiveNetworkInfo(); + if (activeNetInfo == null || !activeNetInfo.isAvailable()) { + return "null"; + } + NetworkInfo wifiInfo = connect.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + if (wifiInfo != null) { + NetworkInfo.State state = wifiInfo.getState(); + if (state != null) + if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) { + return "WIFI"; + } + } + NetworkInfo networkInfo = connect.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); + NetworkInfo.State state = networkInfo.getState(); + if (null != state) + if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) { + switch (activeNetInfo.getSubtype()) { + case TelephonyManager.NETWORK_TYPE_GPRS: // 联通2g + case TelephonyManager.NETWORK_TYPE_CDMA: // 电信2g + case TelephonyManager.NETWORK_TYPE_EDGE: // 移动2g + case TelephonyManager.NETWORK_TYPE_1xRTT: + case TelephonyManager.NETWORK_TYPE_IDEN: + return "2G"; + case TelephonyManager.NETWORK_TYPE_EVDO_A: // 电信3g + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + case TelephonyManager.NETWORK_TYPE_EHRPD: + case TelephonyManager.NETWORK_TYPE_HSPAP: + return "3G"; + case TelephonyManager.NETWORK_TYPE_LTE: + return "4G"; + } + } + return "null"; + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/PicturesCompressor.java b/app/src/main/java/net/oschina/app/improve/utils/PicturesCompressor.java new file mode 100644 index 0000000000000000000000000000000000000000..61d2b850a573bf69ff609f96b0ee31e1ef0a2885 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/PicturesCompressor.java @@ -0,0 +1,227 @@ +package net.oschina.app.improve.utils; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.media.ExifInterface; +import android.util.Log; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.target.Target; + +import net.oschina.app.AppContext; +import net.oschina.app.util.TLog; + +import java.io.File; +import java.io.IOException; + +import static net.oschina.common.utils.StreamUtil.copyFile; + + +/** + * Created by JuQiu + * on 16/7/21. + */ +@SuppressWarnings("WeakerAccess,all") +public final class PicturesCompressor { + private PicturesCompressor() { + + } + + public static boolean compressImage(final String srcPath, + final String savePath, + final long targetSize) { + return compressImage(srcPath, savePath, targetSize, 75, 1280, 1280 * 6, null, null, true); + } + + public static File loadWithGlideCache(String path) { + File tmp; + try { + tmp = Glide.with(AppContext.getInstance()) + .load(path) + .downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .get(); + String absPath = tmp.getAbsolutePath(); + TLog.d("PicturesCompressor", "loadWithGlideCache:" + absPath); + return tmp; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 压缩图片 + * + * @param srcPath 原图地址 + * @param savePath 存储地址 + * @param maxSize 最大文件地址byte + * @param minQuality 最小质量 + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @param byteStorage 用于批量压缩时的buffer,不必要为null, + * 需要时,推荐 {{@link BitmapUtil#DEFAULT_BUFFER_SIZE}} + * @param options 批量压缩时复用参数,可调用 {{@link BitmapUtil#createOptions()}} 得到 + * @param exactDecode 是否精确解码, TRUE: 在4.4及其以上机器中能更节约内存 + * @return 是否压缩成功 + */ + public static boolean compressImage(final String srcPath, + final String savePath, + final long maxSize, + final int minQuality, + final int maxWidth, + final int maxHeight, + byte[] byteStorage, + BitmapFactory.Options options, + boolean exactDecode) { + boolean loadWithGlide = false; + // build source file + File inTmp = new File(srcPath); + final File sourceFile; + if (inTmp.exists()) { + sourceFile = inTmp; + } else { + File tmp = loadWithGlideCache(srcPath); + if (tmp == null) + return false; + sourceFile = tmp; + loadWithGlide = true; + } + + // build save file + final File saveFile = new File(savePath); + File saveDir = saveFile.getParentFile(); + if (!saveDir.exists()) { + if (!saveDir.mkdirs()) + return false; + } + + // End clear the out file data + if (saveFile.exists()) { + if (!saveFile.delete()) + return false; + } + + // if the in file size <= maxSize, we can copy to savePath + if (sourceFile.length() <= maxSize && confirmImage(sourceFile, options) && readPictureDegree(sourceFile.getPath()) == 0) { + return copyFile(sourceFile, saveFile); + } + + File realCacheFile; + if (loadWithGlide) { + realCacheFile = sourceFile; + } else { + realCacheFile = loadWithGlideCache(sourceFile.getAbsolutePath()); + if (realCacheFile == null) + return false; + } + + // Doing + File tempFile = BitmapUtil.Compressor.compressImage(realCacheFile, maxSize, minQuality, maxWidth, + maxHeight, byteStorage, options, exactDecode); + + // Rename to out file + return tempFile != null && copyFile(tempFile, saveFile) && tempFile.delete(); + } + + public static boolean confirmImage(File file, BitmapFactory.Options opts) { + if (opts == null) opts = BitmapUtil.createOptions(); + opts.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file.getAbsolutePath(), opts); + String mimeType = opts.outMimeType.toLowerCase(); + return mimeType.contains("jpeg") || mimeType.contains("png") || mimeType.contains("gif"); + } + + public static String getFileDiff(File file) { + BitmapFactory.Options opts = BitmapUtil.createOptions(); + opts.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file.getAbsolutePath(), opts); + String mimeType = opts.outMimeType.toLowerCase(); + if(mimeType.contains("jpeg")){ + return "jpeg"; + } + if(mimeType.contains("png") ){ + return "png"; + } + if(mimeType.contains("gif")){ + return "gif"; + } + return "jpg"; + } + + public static String verifyPictureExt(String filePath) { + int lastDotIndex = filePath.lastIndexOf("."); + String ext = "jpg"; + String filePathWithoutDot = filePath; + if (lastDotIndex != -1) { + try { + ext = filePath.substring(lastDotIndex + 1).toLowerCase(); + filePathWithoutDot = filePath.substring(lastDotIndex).toLowerCase(); + } catch (Exception e) { + ext = "jpg"; + filePathWithoutDot = filePath; + } + } + + BitmapFactory.Options option = BitmapUtil.createOptions(); + option.inJustDecodeBounds = true; + BitmapFactory.decodeFile(filePath, option); + String mimeType = option.outMimeType.toLowerCase(); + // "x-ico" "webp" "vnd.wap.wbmp" + if (mimeType.contains("jpeg")) { + ext = "jpg"; + } else if (mimeType.contains("png")) { + ext = "png"; + } + + String newFilePath = String.format("%s.%s", filePathWithoutDot, ext); + + if (!filePath.equals(newFilePath)) { + if (new File(filePath).renameTo(new File(newFilePath))) + return newFilePath; + } + return filePath; + } + + + /** + * 获取图片的旋转角度 + * + * @param path path + * @return 图片的旋转角度 + */ + public static int readPictureDegree(String path) { + int degree = 0; + try { + ExifInterface exifInterface = new ExifInterface(path); + int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_90: + degree = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + degree = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + degree = 270; + break; + } + } catch (IOException e) { + e.printStackTrace(); + } + return degree; + } + + private static Bitmap rotaingBitmap(int angle, Bitmap bitmap) { + //旋转图片 动作 + Matrix matrix = new Matrix(); + ; + matrix.postRotate(angle); + Log.e("angle2", " -- " + angle); + // 创建新的图片 + Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, + bitmap.getWidth(), bitmap.getHeight(), matrix, true); + bitmap.recycle(); + return resizedBitmap; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/Platform.java b/app/src/main/java/net/oschina/app/improve/utils/Platform.java new file mode 100644 index 0000000000000000000000000000000000000000..0a04a853ac7af6130b2758a382479024008e926e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/Platform.java @@ -0,0 +1,42 @@ +package net.oschina.app.improve.utils; + +import net.oschina.app.AppContext; +import net.oschina.app.R; + +/** + * 平台 + * Created by huanghaibin on 2017/11/14. + */ + +public final class Platform { + private final static int CLIENT_MOBILE = 2; + private final static int CLIENT_ANDROID = 3; + private final static int CLIENT_IPHONE = 4; + private final static int CLIENT_WINDOWS_PHONE = 5; + private final static int CLIENT_WECHAT = 6; + + public static String getPlatform(int platfrom) { + int resId; + switch (platfrom) { + case CLIENT_MOBILE: + resId = R.string.from_mobile; + break; + case CLIENT_ANDROID: + resId = R.string.from_android; + break; + case CLIENT_IPHONE: + resId = R.string.from_iphone; + break; + case CLIENT_WINDOWS_PHONE: + resId = R.string.from_windows_phone; + break; + case CLIENT_WECHAT: + resId = R.string.from_wechat; + break; + default: + return ""; + } + return AppContext.getInstance().getResources().getString(resId); + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/QuickOptionDialogHelper.java b/app/src/main/java/net/oschina/app/improve/utils/QuickOptionDialogHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..765084b64131aea4e82ba59fe1c0cefd5b7c5248 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/QuickOptionDialogHelper.java @@ -0,0 +1,73 @@ +package net.oschina.app.improve.utils; + +import android.content.Context; +import android.content.DialogInterface; +import android.support.annotation.StringRes; +import android.text.TextUtils; + +import net.oschina.app.R; +import net.oschina.app.util.TDevice; +import net.oschina.common.utils.CollectionUtil; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ + +public class QuickOptionDialogHelper { + private List items = new ArrayList<>(); + private List runnableList = new ArrayList<>(); + private Context context; + + public static QuickOptionDialogHelper with(Context context) { + QuickOptionDialogHelper helper = new QuickOptionDialogHelper(); + helper.context = context; + return helper; + } + + public QuickOptionDialogHelper addCopy(final String text) { + if (TextUtils.isEmpty(text)) + return this; + addOther(R.string.copy, new Runnable() { + @Override + public void run() { + TDevice.copyTextToBoard(text); + } + }); + return this; + } + + public QuickOptionDialogHelper addOther(@StringRes int id, Runnable runnable) { + items.add(context.getString(id)); + runnableList.add(runnable); + return this; + } + + public QuickOptionDialogHelper addOther(boolean isNeed, @StringRes int id, Runnable runnable) { + if (!isNeed) + return this; + return addOther(id, runnable); + } + + + public void show() { + if (items.size() == 0) + return; + DialogHelper.getSelectDialog(context, CollectionUtil.toArray(items, String.class), "取消", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + doWork(i); + } + }).show(); + } + + private void doWork(int index) { + if (index >= 0 && index < runnableList.size()) { + runnableList.get(index).run(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/ReadedIndexCacheManager.java b/app/src/main/java/net/oschina/app/improve/utils/ReadedIndexCacheManager.java new file mode 100644 index 0000000000000000000000000000000000000000..861264e80aa9328cbc7e74f48d1319706d11d3d2 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/ReadedIndexCacheManager.java @@ -0,0 +1,146 @@ +package net.oschina.app.improve.utils; + +import android.content.Context; +import android.util.Pair; + +import com.google.gson.reflect.TypeToken; + +import net.oschina.app.improve.app.AppOperator; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * 已读位置管理类 + *

    + * 博客和资讯 + * Created by thanatosx on 2016/12/27. + */ + +public class ReadedIndexCacheManager { + + public static final String FILE_NAME = ReadedIndexCacheManager.class.getSimpleName(); + public static final int MIN_LIMIT_READED_POSITION = 30; + public static final int MAX_LIMIT_READED_COUNT = 50; + public static final int LIMIT_READED_CLEAR_COUNT = 30; + public static List> pairs; + + /** + * 得到缓存的已读位置Key-Value + * + * @param context Context + * @return {@link List>} + */ + public static List> getPairs(Context context) { + if (pairs == null) { + pairs = CacheManager.readJson(context, FILE_NAME + , new TypeToken>>() { + }.getType()); + if (pairs == null) pairs = new ArrayList<>(); + } + return pairs; + } + + /** + * 得到某篇文章的已读位置 + * + * @param context {@link Context} + * @param id The Article Id + * @param type The Article Type {@link net.oschina.app.api.remote.OSChinaApi#CATALOG_NEWS} + * {@link net.oschina.app.api.remote.OSChinaApi#CATALOG_BLOG} + * @return 已读位置 + */ + public static int getIndex(Context context, long id, int type) { + String in = getIndexName(id, type); + List> pairs = getPairs(context); + for (Pair pair : pairs) { + if (pair.first.equals(in)) return pair.second; + } + return 0; + } + + /** + * 保存文章的已读位置 + * + * @param context Context + * @param id The Article Id + * @param type The Article Type The Article Type {@link net.oschina.app.api.remote.OSChinaApi#CATALOG_NEWS} + * {@link net.oschina.app.api.remote.OSChinaApi#CATALOG_BLOG} + * @param index 已读位置, 已读位置小于等于{@link #MIN_LIMIT_READED_POSITION} 将会移除储存的已读位置 + */ + public static void saveIndex(Context context, long id, int type, int index) { + String in = getIndexName(id, type); + if (index <= MIN_LIMIT_READED_POSITION) { + removeIndex(context, in); + return; + } + List> pairs = getPairs(context); + + // 去重 + Iterator> iterator = pairs.iterator(); + while (iterator.hasNext()) { + Pair pair = iterator.next(); + if (pair.first.equals(in)) { + iterator.remove(); + break; + } + } + + pairs.add(0, Pair.create(in, index)); + + if (pairs.size() > MAX_LIMIT_READED_COUNT) { + while (pairs.size() > LIMIT_READED_CLEAR_COUNT) { + pairs.remove(pairs.size() - 1); + } + } + save(context.getApplicationContext(), new ArrayList<>(pairs)); + } + + /** + * 移除指定文章的已读位置 + * + * @param context Context + * @param in Create By {@link #getIndexName(long, int)} + */ + public static void removeIndex(Context context, String in) { + List> pairs = getPairs(context); + Iterator> iterator = pairs.iterator(); + while (iterator.hasNext()) { + Pair pair = iterator.next(); + if (pair.first.equals(in)) { + iterator.remove(); + save(context.getApplicationContext(), new ArrayList<>(pairs)); + break; + } + } + } + + /** + * 保存已读位置 + * + * @param context Context + * @param pairs {@link #pairs} + */ + private static void save(final Context context, final List> pairs) { + AppOperator.getExecutor().execute(new Runnable() { + @Override + public void run() { + CacheManager.saveToJson(context, FILE_NAME, pairs); + } + }); + } + + /** + * Key Name Scheme + * + * @param id The Article id + * @param type The Article Type + * @return Key Name Scheme + */ + public static String getIndexName(long id, int type) { + return String.format("%s-%s", id, type); + } + + +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/SoftKeyboardUtil.java b/app/src/main/java/net/oschina/app/improve/utils/SoftKeyboardUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..a4af0fdf0c6a8e4773c750a7a0e121d9227e5fef --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/SoftKeyboardUtil.java @@ -0,0 +1,343 @@ +package net.oschina.app.improve.utils; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Build; +import android.view.Display; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; + +import net.oschina.app.Setting; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.TLog; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + *

    + * PS: Most code form WeChat + */ +@SuppressWarnings("WeakerAccess") +public class SoftKeyboardUtil { + private static int LAST_SAVE_KEYBOARD_HEIGHT = 0; + + private static boolean saveKeyboardHeight(final Context context, int keyboardHeight) { + if (LAST_SAVE_KEYBOARD_HEIGHT == keyboardHeight) { + return false; + } + + if (keyboardHeight < 0) { + return false; + } + + LAST_SAVE_KEYBOARD_HEIGHT = keyboardHeight; + Setting.updateSoftKeyboardHeight(context, keyboardHeight); + return true; + } + + public static int getKeyboardHeight(final Context context) { + if (LAST_SAVE_KEYBOARD_HEIGHT == 0) { + LAST_SAVE_KEYBOARD_HEIGHT = Setting.getSoftKeyboardHeight(context); + if (LAST_SAVE_KEYBOARD_HEIGHT == 0) + LAST_SAVE_KEYBOARD_HEIGHT = getMinPanelHeight(context.getResources()); + } + return LAST_SAVE_KEYBOARD_HEIGHT; + } + + public static int getValidPanelHeight(final Context context) { + final int maxPanelHeight = getMaxPanelHeight(context.getResources()); + final int minPanelHeight = getMinPanelHeight(context.getResources()); + + int validPanelHeight = getKeyboardHeight(context); + + validPanelHeight = Math.max(minPanelHeight, validPanelHeight); + validPanelHeight = Math.min(maxPanelHeight, validPanelHeight); + return validPanelHeight; + } + + private static int MAX_PANEL_HEIGHT = 0; + private static int MIN_PANEL_HEIGHT = 0; + private static int MIN_KEYBOARD_HEIGHT = 0; + + public static int getMaxPanelHeight(final Resources res) { + if (MAX_PANEL_HEIGHT == 0) { + MAX_PANEL_HEIGHT = (int) TDevice.dipToPx(res, 300); + } + + return MAX_PANEL_HEIGHT; + } + + public static int getMinPanelHeight(final Resources res) { + if (MIN_PANEL_HEIGHT == 0) { + MIN_PANEL_HEIGHT = (int) TDevice.dipToPx(res, 92); + } + + return MIN_PANEL_HEIGHT; + } + + public static int getMinKeyboardHeight(Context context) { + if (MIN_KEYBOARD_HEIGHT == 0) { + MIN_KEYBOARD_HEIGHT = (int) TDevice.dipToPx(context.getResources(), 56); + } + return MIN_KEYBOARD_HEIGHT; + } + + public static ViewTreeObserver.OnGlobalLayoutListener attach(final Activity activity, IPanelHeightTarget target) { + final ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content); + final boolean isFullScreen = UiUtil.isFullScreen(activity); + final boolean isTranslucentStatus = UiUtil.isTranslucentStatus(activity); + final boolean isFitSystemWindows = UiUtil.isFitsSystemWindows(activity); + + // get the screen height. + final Display display = activity.getWindowManager().getDefaultDisplay(); + final Point screenSize = new Point(); + display.getSize(screenSize); + final int screenHeight = screenSize.y; + + + ViewTreeObserver.OnGlobalLayoutListener globalLayoutListener = new KeyboardStatusListener( + isFullScreen, + isTranslucentStatus, + isFitSystemWindows, + contentView, + target, + screenHeight); + + contentView.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener); + return globalLayoutListener; + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public static void detach(Activity activity, ViewTreeObserver.OnGlobalLayoutListener l) { + ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content); + contentView.getViewTreeObserver().removeOnGlobalLayoutListener(l); + } + + private static class KeyboardStatusListener implements ViewTreeObserver.OnGlobalLayoutListener { + private final static String TAG = "KeyboardStatusListener"; + + private int previousDisplayHeight = 0; + private final ViewGroup contentView; + private final IPanelHeightTarget panelHeightTarget; + private final boolean isFullScreen; + private final boolean isTranslucentStatus; + private final boolean isFitSystemWindows; + private final int statusBarHeight; + private boolean lastKeyboardShowing; + private final int screenHeight; + + private boolean isOverlayLayoutDisplayHContainStatusBar = false; + + KeyboardStatusListener(boolean isFullScreen, + boolean isTranslucentStatus, + boolean isFitSystemWindows, + ViewGroup contentView, + IPanelHeightTarget panelHeightTarget, + int screenHeight) { + this.contentView = contentView; + this.panelHeightTarget = panelHeightTarget; + this.isFullScreen = isFullScreen; + this.isTranslucentStatus = isTranslucentStatus; + this.isFitSystemWindows = isFitSystemWindows; + this.statusBarHeight = UiUtil.getStatusBarHeight(contentView.getContext()); + this.screenHeight = screenHeight; + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) + @Override + public void onGlobalLayout() { + final View userRootView = contentView.getChildAt(0); + final View actionBarOverlayLayout = (View) contentView.getParent(); + + // Step 1. calculate the current display frame's height. + Rect r = new Rect(); + + final int displayHeight; + if (isTranslucentStatus) { + // status bar translucent. + + // In the case of the Theme is Status-Bar-Translucent, we calculate the keyboard + // state(showing/hiding) and the keyboard height based on assuming that the + // displayHeight includes the height of the status bar. + actionBarOverlayLayout.getWindowVisibleDisplayFrame(r); + + final int overlayLayoutDisplayHeight = (r.bottom - r.top); + + if (!isOverlayLayoutDisplayHContainStatusBar) { + // in case of the keyboard is hiding, the display height of the + // action-bar-overlay-layout would be possible equal to the screen height. + + // and if isOverlayLayoutDisplayHContainStatusBar has already been true, the + // display height of action-bar-overlay-layout must include the height of the + // status bar always. + isOverlayLayoutDisplayHContainStatusBar = overlayLayoutDisplayHeight == screenHeight; + } + + if (!isOverlayLayoutDisplayHContainStatusBar) { + // In normal case, we need to plus the status bar height manually. + displayHeight = overlayLayoutDisplayHeight + statusBarHeight; + } else { + // In some case(such as Samsung S7 edge), the height of the action-bar-overlay-layout + // display bound already included the height of the status bar, in this case we + // doesn't need to plus the status bar height manually. + displayHeight = overlayLayoutDisplayHeight; + } + + } else { + userRootView.getWindowVisibleDisplayFrame(r); + displayHeight = (r.bottom - r.top); + } + + calculateKeyboardHeight(displayHeight); + calculateKeyboardShowing(displayHeight); + + previousDisplayHeight = displayHeight; + } + + private void calculateKeyboardHeight(final int displayHeight) { + // first result. + if (previousDisplayHeight == 0) { + previousDisplayHeight = displayHeight; + + // init the panel height for target. + panelHeightTarget.refreshHeight(SoftKeyboardUtil.getValidPanelHeight(getContext())); + return; + } + + int keyboardHeight; + if (isHandleByPlaceholder(isFullScreen, isTranslucentStatus, + isFitSystemWindows)) { + // the height of content parent = contentView.height + actionBar.height + final View actionBarOverlayLayout = (View) contentView.getParent(); + + keyboardHeight = actionBarOverlayLayout.getHeight() - displayHeight; + + TLog.d(TAG, String.format("action bar over layout %s display height: %s", + ((View) contentView.getParent()).getHeight(), displayHeight)); + + } else { + keyboardHeight = Math.abs(displayHeight - previousDisplayHeight); + } + // no change. + if (keyboardHeight <= getMinKeyboardHeight(getContext())) { + return; + } + + TLog.d(TAG, String.format("pre display height: %s display height: %s keyboard: %s ", + previousDisplayHeight, displayHeight, keyboardHeight)); + + // influence from the layout of the Status-bar. + if (keyboardHeight == this.statusBarHeight) { + TLog.e(TAG, String.format("On global layout change get keyboard height just equal" + + " statusBar height %s", keyboardHeight)); + return; + } + + // save the keyboardHeight + boolean changed = SoftKeyboardUtil.saveKeyboardHeight(getContext(), keyboardHeight); + if (changed) { + final int validPanelHeight = SoftKeyboardUtil.getValidPanelHeight(getContext()); + if (this.panelHeightTarget.getPanelHeight() != validPanelHeight) { + // Step3. refresh the panel's height with valid-panel-height which refer to + // the last keyboard height + this.panelHeightTarget.refreshHeight(validPanelHeight); + } + } + } + + private int maxOverlayLayoutHeight; + + private void calculateKeyboardShowing(final int displayHeight) { + boolean isKeyboardShowing; + + // the height of content parent = contentView.height + actionBar.height + final View actionBarOverlayLayout = (View) contentView.getParent(); + // in the case of FragmentLayout, this is not real ActionBarOverlayLayout, it is + // LinearLayout, and is a child of DecorView, and in this case, its top-padding would be + // equal to the height of status bar, and its height would equal to DecorViewHeight - + // NavigationBarHeight. + final int actionBarOverlayLayoutHeight = actionBarOverlayLayout.getHeight() - + actionBarOverlayLayout.getPaddingTop(); + + if (isHandleByPlaceholder(isFullScreen, isTranslucentStatus, + isFitSystemWindows)) { + if (!isTranslucentStatus && + actionBarOverlayLayoutHeight - displayHeight == this.statusBarHeight) { + // handle the case of status bar layout, not keyboard active. + isKeyboardShowing = lastKeyboardShowing; + } else { + isKeyboardShowing = actionBarOverlayLayoutHeight > displayHeight; + } + } else { + + final int phoneDisplayHeight = contentView.getResources().getDisplayMetrics().heightPixels; + if (!isTranslucentStatus && + phoneDisplayHeight == actionBarOverlayLayoutHeight) { + // no space to settle down the status bar, switch to fullscreen, + // only in the case of paused and opened the fullscreen page. + TLog.e(TAG, String.format("skip the keyboard status calculate, the current" + + " activity is paused. and phone-display-height %d," + + " root-height+actionbar-height %d", phoneDisplayHeight, + actionBarOverlayLayoutHeight)); + return; + + } + + if (maxOverlayLayoutHeight == 0) { + // non-used. + isKeyboardShowing = lastKeyboardShowing; + } else { + isKeyboardShowing = displayHeight < maxOverlayLayoutHeight - getMinKeyboardHeight(getContext()); + } + + maxOverlayLayoutHeight = Math.max(maxOverlayLayoutHeight, actionBarOverlayLayoutHeight); + } + + if (lastKeyboardShowing != isKeyboardShowing) { + TLog.d(TAG, String.format("displayHeight %s actionBarOverlayLayoutHeight %s " + + "keyboard status change: %s", + displayHeight, actionBarOverlayLayoutHeight, isKeyboardShowing)); + this.panelHeightTarget.onKeyboardShowing(isKeyboardShowing); + } + + lastKeyboardShowing = isKeyboardShowing; + + } + + private Context getContext() { + return contentView.getContext(); + } + } + + + private static boolean isHandleByPlaceholder(boolean isFullScreen, boolean isTranslucentStatus, + boolean isFitsSystem) { + return isFullScreen || (isTranslucentStatus && !isFitsSystem); + } + + public interface IPanelHeightTarget { + + /** + * for handle the panel's height, will be equal to the keyboard height which had saved last time. + */ + void refreshHeight(final int panelHeight); + + /** + * @return get the height of target-view. + */ + int getPanelHeight(); + + /** + * Be invoked by onGlobalLayoutListener call-back. + * + * @param showing whether the keyboard is showing or not. + */ + void onKeyboardShowing(boolean showing); + } + +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/utils/UI.java b/app/src/main/java/net/oschina/app/improve/utils/UI.java new file mode 100644 index 0000000000000000000000000000000000000000..859cac74e4ecec19a1afe51837804df8a7a22ab3 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/UI.java @@ -0,0 +1,21 @@ +package net.oschina.app.improve.utils; + +import android.os.Handler; +import android.os.Looper; + +/** + * UI操作相关 + * Created by huanghaibin on 2018/1/18. + */ +@SuppressWarnings("unused") +public final class UI { + private static final Handler mHandler = new Handler(Looper.getMainLooper()); + + public static void runOnMainThread(Runnable runnable) { + mHandler.post(runnable); + } + + public static void runDelayed(Runnable runnable, long delay) { + mHandler.postDelayed(runnable, delay); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/UIFormat.java b/app/src/main/java/net/oschina/app/improve/utils/UIFormat.java new file mode 100644 index 0000000000000000000000000000000000000000..ce997e241bf5b0397fed62042cb26aea005faf60 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/UIFormat.java @@ -0,0 +1,326 @@ +package net.oschina.app.improve.utils; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; + +import net.oschina.app.bean.SimpleBackPage; +import net.oschina.app.fragment.QuestionTagFragment; +import net.oschina.app.improve.detail.general.BlogDetailActivity; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.improve.detail.general.NewsDetailActivity; +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.main.synthesize.web.WebActivity; +import net.oschina.app.improve.media.ImageGalleryActivity; +import net.oschina.app.improve.tweet.activities.TopicActivity; +import net.oschina.app.improve.tweet.activities.TweetDetailActivity; +import net.oschina.app.improve.tweet.fragments.TweetFragment; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.ui.SimpleBackActivity; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.UIHelper; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * UI解析类 + * Created by huanghaibin on 2017/12/12. + */ +@SuppressWarnings("unused") +public final class UIFormat { + + private static final Pattern PATTERN_URL = Pattern.compile( + "(?:http|https)://([^/]+)(.+)" + ); + + private static final Pattern PATTERN_PATH_NEWS = Pattern.compile( + "/news/([0-9]+).*" + ); + + private static final Pattern PATTERN_PATH_SOFTWARE = Pattern.compile( + "/p/([^/]+)" + ); + + private static final Pattern PATTERN_PATH_TOPIC = Pattern.compile( + "/question/tag/(\\w+)" + ); + + private static final Pattern PATTERN_PATH_TWEET_TOPIC = Pattern.compile( + "/tweet-topic/([^/]+)" + ); + + private static final Pattern PATTERN_PATH_QUESTION = Pattern.compile( + "/question/(\\w+)" + ); + + private static final Pattern PATTERN_PATH_USER_BLOG = Pattern.compile( + "/([^/]+)/blog/([0-9]+)" + ); + + private static final Pattern PATTERN_PATH_USER_TWEET = Pattern.compile( + "/([^/]+)/tweet/([0-9]+)" + ); + + private static final Pattern PATTERN_PATH_USER_UID = Pattern.compile( + "/u/([0-9]+)" + ); + + private static final Pattern PATTERN_PATH_USER_SUFFIX = Pattern.compile( + "/([^/]+)" + ); + + private static final Pattern PATTERN_PATH_CITY_EVENT = Pattern.compile( + "/([^/]+)/event/([0-9]+)" + ); + + private static final Pattern PATTERN_PATH_EVENT = Pattern.compile( + "/event/([0-9]+)" + ); + + private static final Pattern PATTERN_IMAGE = Pattern.compile( + ".*?(gif|jpeg|png|jpg|bmp)" + ); + + private static final Pattern PATTERN_GIT = Pattern.compile( + ".*(git.oschina.net|gitee.com)/(.*)/(.*)" + ); + + private static final String PREFIX_IMAGE = "ima-api:action=showImage&data="; + + + public static void show(Context context, String url) { + if (TextUtils.isEmpty(url)) { + return; + } + if (checkIsDownload(url)) { + openExternalBrowser(context, url); + return; + } + + if (!url.startsWith("http://") && !url.startsWith("https://")) { + url = "https://" + url; + } + try { + url = URLDecoder.decode(url, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + Matcher matcher; + + // image url ? + matcher = PATTERN_IMAGE.matcher(url); + if (matcher.matches()) { + ImageGalleryActivity.show(context, url); + return; + } + + matcher = PATTERN_URL.matcher(url); + if (!matcher.find()) { + parseNonstandardUrl(context, url); + return; + } + + // own ? + String host = matcher.group(1); + String path = matcher.group(2); + + if (TextUtils.isEmpty(host) || TextUtils.isEmpty(path)) { + return; + } + + long oid; + switch (host) { + case "www.oschina.net": + matcher = PATTERN_PATH_NEWS.matcher(path); + if (matcher.find()) { + oid = StringUtils.toLong(matcher.group(1)); + NewsDetailActivity.show(context, oid); + break; + } + matcher = PATTERN_PATH_SOFTWARE.matcher(path); + if (matcher.find()) { + SoftwareDetailActivity.show(context, matcher.group(1)); + break; + } + matcher = PATTERN_PATH_TOPIC.matcher(path); + if (matcher.find()) { + showPostListByTag(context, matcher.group(1)); + break; + } + matcher = PATTERN_PATH_TWEET_TOPIC.matcher(path); + if (matcher.find()) { + // TODO replace by new activity + TopicActivity.show(context, TweetFragment.CATALOG_TAG, matcher.group(1)); + break; + } + matcher = PATTERN_PATH_QUESTION.matcher(path); + if (matcher.find()) { + oid = StringUtils.toLong(matcher.group(1).split("_")[1]); + QuestionDetailActivity.show(context, oid); + break; + } + matcher = PATTERN_PATH_EVENT.matcher(path); + if (matcher.find()) { + oid = StringUtils.toLong(matcher.group(1)); + if (oid > 0) { + EventDetailActivity.show(context, oid); + break; + } + } + openAPPBrowser(context, url); + break; + case "team.oschina.net": + // TODO team要独立? + openAPPBrowser(context, url); + break; + case "git.oschina.net": + // TODO 如果用户安装了git@osc application, 使用git@osc打开 + case "gitee.com": +// Matcher matcherGit = PATTERN_GIT.matcher(uri); +// if (matcherGit.find() && matcherGit.groupCount() >= 2) { +// String group1 = matcherGit.group(2); +// String group2 = matcherGit.group(3); +// if ("explore".equals(group1) || "gists".equals(group1) || "enterprises".equals(group1)) { +// UIHelper.openInternalBrowser(context, url); +// } else { +// ProjectDetailActivity.show(context, group1, group2, uri); +// } +// } else { +// UIHelper.openInternalBrowser(context, url); +// } + openAPPBrowser(context, TypeFormat.formatUrl(url)); + break; + case "my.oschina.net": + matcher = PATTERN_PATH_USER_BLOG.matcher(path); + if (matcher.find()) { + oid = StringUtils.toLong(matcher.group(2)); + BlogDetailActivity.show(context, oid); + break; + } + matcher = PATTERN_PATH_USER_TWEET.matcher(path); + if (matcher.find()) { + oid = StringUtils.toLong(matcher.group(2)); + TweetDetailActivity.show(context, oid); + break; + } + matcher = PATTERN_PATH_USER_UID.matcher(path); + if (matcher.find()) { + oid = StringUtils.toLong(matcher.group(1)); + OtherUserHomeActivity.show(context, oid); + break; + } + matcher = PATTERN_PATH_USER_SUFFIX.matcher(path); + if (matcher.find()) { + OtherUserHomeActivity.show(context, 0, matcher.group(1)); + break; + } + UIHelper.openInternalBrowser(context, url); + break; + case "city.oschina.net": + matcher = PATTERN_PATH_CITY_EVENT.matcher(url); + if (matcher.find()) { + long eid = StringUtils.toInt(matcher.group(2), 0); + if (eid <= 0) return; + UIHelper.showEventDetail(context, eid); + return; + } + openAPPBrowser(context, url); + break; + default: + openAPPBrowser(context, url); + } + } + + + private static boolean checkIsDownload(String url) { + if (TextUtils.isEmpty(url)) + return false; + for (String suffix : SUFFIX) { + if (url.endsWith(suffix)) + return true; + } + return false; + } + + private static final String[] SUFFIX = new String[]{".apk", ".zip", ".rar", + ".mp3", ".mp4", ".rm", ".rmvb", ".avi", ".7z", ".flv", ".iso", ".txt", ".pdf"}; + + + /** + * 打开内置浏览器 + * + * @param context context + * @param url url + */ + private static void openAPPBrowser(Context context, String url) { + WebActivity.show(context, url); + } + + /** + * 打开外置的浏览器 + * + * @param context context + * @param url url + */ + private static void openExternalBrowser(Context context, String url) { + Uri uri = Uri.parse(url); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + context.startActivity(Intent.createChooser(intent, "选择打开的应用")); + } + + + private static void parseNonstandardUrl(Context context, String url) { + + if (url.startsWith("mailto:")) { + Uri uri = Uri.parse(url); + Intent intent = new Intent(Intent.ACTION_SENDTO, uri); + context.startActivity(Intent.createChooser(intent, "选择发送应用")); + return; + } + + // image, 我不懂老代码的思路...所以直接copy过来 + if (url.startsWith(PREFIX_IMAGE)) { + String jos = url.substring(PREFIX_IMAGE.length()); + try { + JSONObject json = new JSONObject(jos); + String[] urls = json.getString("urls").split(","); + ImageGalleryActivity.show(context, urls[0]); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + + + /** + * 显示相关Tag帖子列表 + * + * @param context context + * @param tag tag + */ + private static void showPostListByTag(Context context, String tag) { + Bundle args = new Bundle(); + args.putString(QuestionTagFragment.BUNDLE_KEY_TAG, tag); + showSimpleBack(context, SimpleBackPage.QUESTION_TAG, args); + } + + public static void showSimpleBack(Context context, SimpleBackPage page, + Bundle args) { + Intent intent = new Intent(context, SimpleBackActivity.class); + intent.putExtra(SimpleBackActivity.BUNDLE_KEY_ARGS, args); + intent.putExtra(SimpleBackActivity.BUNDLE_KEY_PAGE, page.getValue()); + context.startActivity(intent); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/URLUtils.java b/app/src/main/java/net/oschina/app/improve/utils/URLUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..25e133ffac870367f9a8c9da4255b2585c8318d5 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/URLUtils.java @@ -0,0 +1,269 @@ +package net.oschina.app.improve.utils; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.text.TextUtils; + +import net.oschina.app.improve.detail.general.QuestionDetailActivity; +import net.oschina.app.improve.detail.general.SoftwareDetailActivity; +import net.oschina.app.improve.main.synthesize.TypeFormat; +import net.oschina.app.improve.main.synthesize.web.WebActivity; +import net.oschina.app.improve.media.ImageGalleryActivity; +import net.oschina.app.improve.tweet.activities.TopicActivity; +import net.oschina.app.improve.tweet.activities.TweetDetailActivity; +import net.oschina.app.improve.tweet.fragments.TweetFragment; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.util.StringUtils; +import net.oschina.app.util.UIHelper; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 解析URL链接, 如果是站内链接, 用相应的activity打开, 否则用浏览器打开 + * Created by thanatos on 16/9/27. + */ + +public class URLUtils { + + public static final Pattern PATTERN_URL = Pattern.compile( + "(?:http|https)://([^/]+)(.+)" + ); + + public static final Pattern PATTERN_PATH_NEWS = Pattern.compile( + "/news/([0-9]+).*" + ); + + public static final Pattern PATTERN_PATH_SOFTWARE = Pattern.compile( + "/p/([^/]+)" + ); + + public static final Pattern PATTERN_PATH_TOPIC = Pattern.compile( + "/question/tag/(\\w+)" + ); + + public static final Pattern PATTERN_PATH_TWEET_TOPIC = Pattern.compile( + "/tweet-topic/([^/]+)" + ); + + public static final Pattern PATTERN_PATH_QUESTION = Pattern.compile( + "/question/[0-9]+_([0-9]+)]" + ); + + public static final Pattern PATTERN_PATH_USER_BLOG = Pattern.compile( + "/([^/]+)/blog/([0-9]+)" + ); + + public static final Pattern PATTERN_PATH_USER_TWEET = Pattern.compile( + "/([^/]+)/tweet/([0-9]+)" + ); + + public static final Pattern PATTERN_PATH_USER_UID = Pattern.compile( + "/u/([0-9]+)" + ); + + public static final Pattern PATTERN_PATH_USER_SUFFIX = Pattern.compile( + "/([^/]+)" + ); + + public static final Pattern PATTERN_PATH_CITY_EVENT = Pattern.compile( + "/([^/]+)/event/([0-9]+)" + ); + + public static final Pattern PATTERN_PATH_EVENT = Pattern.compile( + "/event/([0-9]+)" + ); + + public static final Pattern PATTERN_IMAGE = Pattern.compile( + ".*?(gif|jpeg|png|jpg|bmp)" + ); + + private static final Pattern PATTERN_GIT = Pattern.compile( + ".*(git.oschina.net|gitee.com)/(.*)/(.*)" + ); + + private static final String PREFIX_IMAGE = "ima-api:action=showImage&data="; + + + /** + * 解析跳转链接, 使用对应应用打开 + * + * @param context Context + * @param uri give me a uri + */ + public static void parseUrl(Context context, String uri) { + if (TextUtils.isEmpty(uri)) return; + + String url = uri; + if (!url.startsWith("http://") && !url.startsWith("https://")) { + url = "https://" + url; + } + try { + url = URLDecoder.decode(url, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // do nothing + } + + Matcher matcher; + + // image url ? + matcher = PATTERN_IMAGE.matcher(url); + if (matcher.matches()) { + ImageGalleryActivity.show(context, url); + return; + } + + matcher = PATTERN_URL.matcher(url); + if (!matcher.find()) { + // other ? + parseNonstandardUrl(context, uri); + return; + } + + // own ? + String host = matcher.group(1); + String path = matcher.group(2); + + if (TextUtils.isEmpty(host) || TextUtils.isEmpty(path)) return; + + long oid = 0; + switch (host) { + case "www.oschina.net": + matcher = PATTERN_PATH_NEWS.matcher(path); + if (matcher.find()) { + oid = StringUtils.toLong(matcher.group(1)); + net.oschina.app.improve.detail.general.NewsDetailActivity.show(context, oid); + break; + } + matcher = PATTERN_PATH_SOFTWARE.matcher(path); + if (matcher.find()) { + // https://www.oschina.net/p/parallels-desktop + SoftwareDetailActivity.show(context, matcher.group(1)); + break; + } + matcher = PATTERN_PATH_TOPIC.matcher(path); + if (matcher.find()) { + // TODO replace by new activity + UIHelper.showPostListByTag(context, matcher.group(1)); + break; + } + matcher = PATTERN_PATH_TWEET_TOPIC.matcher(path); + if (matcher.find()) { + // TODO replace by new activity + // https://www.oschina.net/tweet-topic/Navicat+for+Postgresql +// Bundle bundle = new Bundle(); +// bundle.putInt(TweetFragment.BUNDLE_KEY_REQUEST_CATALOG, TweetFragment.CATALOG_TAG); +// bundle.putString(TweetFragment.BUNDLE_KEY_TAG, matcher.group(1)); +// UIHelper.showSimpleBack(context, SimpleBackPage.TWEET_TOPIC_LIST, bundle); + TopicActivity.show(context, TweetFragment.CATALOG_TAG, matcher.group(1)); + break; + } + matcher = PATTERN_PATH_QUESTION.matcher(path); + if (matcher.find()) { + oid = StringUtils.toLong(matcher.group(1)); + QuestionDetailActivity.show(context, oid); + break; + } + matcher = PATTERN_PATH_EVENT.matcher(path); + if (matcher.find()) { + oid = StringUtils.toLong(matcher.group(1)); + if (oid > 0) { + UIHelper.showEventDetail(context, oid); + break; + } + } + UIHelper.openInternalBrowser(context, url); + break; + case "team.oschina.net": + // TODO team要独立? + UIHelper.openInternalBrowser(context, url); + break; + case "git.oschina.net": + // TODO 如果用户安装了git@osc application, 使用git@osc打开 + case "gitee.com": +// Matcher matcherGit = PATTERN_GIT.matcher(uri); +// if (matcherGit.find() && matcherGit.groupCount() >= 2) { +// String group1 = matcherGit.group(2); +// String group2 = matcherGit.group(3); +// if ("explore".equals(group1) || "gists".equals(group1) || "enterprises".equals(group1)) { +// UIHelper.openInternalBrowser(context, url); +// } else { +// ProjectDetailActivity.show(context, group1, group2, uri); +// } +// } else { +// UIHelper.openInternalBrowser(context, url); +// } + WebActivity.show(context, TypeFormat.formatUrl(url)); + break; + case "my.oschina.net": + matcher = PATTERN_PATH_USER_BLOG.matcher(path); + if (matcher.find()) { + oid = StringUtils.toLong(matcher.group(2)); + net.oschina.app.improve.detail.general.BlogDetailActivity.show(context, oid); + break; + } + matcher = PATTERN_PATH_USER_TWEET.matcher(path); + if (matcher.find()) { + oid = StringUtils.toLong(matcher.group(2)); + TweetDetailActivity.show(context, oid); + break; + } + matcher = PATTERN_PATH_USER_UID.matcher(path); + if (matcher.find()) { + oid = StringUtils.toLong(matcher.group(1)); + OtherUserHomeActivity.show(context, oid); + break; + } + matcher = PATTERN_PATH_USER_SUFFIX.matcher(path); + if (matcher.find()) { + OtherUserHomeActivity.show(context, 0, matcher.group(1)); + break; + } + UIHelper.openInternalBrowser(context, url); + break; + case "city.oschina.net": + matcher = PATTERN_PATH_CITY_EVENT.matcher(url); + if (matcher.find()) { + long eid = StringUtils.toInt(matcher.group(2), 0); + if (eid <= 0) return; + UIHelper.showEventDetail(context, eid); + return; + } + UIHelper.openInternalBrowser(context, url); + break; + default: + // pass + UIHelper.openInternalBrowser(context, url); + } + + } + + public static void parseNonstandardUrl(Context context, String url) { + + if (url.startsWith("mailto:")) { + Uri uri = Uri.parse(url); + Intent intent = new Intent(Intent.ACTION_SENDTO, uri); + context.startActivity(Intent.createChooser(intent, "选择发送应用")); + return; + } + + // image, 我不懂老代码的思路...所以直接copy过来 + if (url.startsWith(PREFIX_IMAGE)) { + String jos = url.substring(PREFIX_IMAGE.length()); + try { + JSONObject json = new JSONObject(jos); + String[] urls = json.getString("urls").split(","); + ImageGalleryActivity.show(context, urls[0]); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/UiUtil.java b/app/src/main/java/net/oschina/app/improve/utils/UiUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..8ea1702d20896054723f0f4b3dd5261a10077b46 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/UiUtil.java @@ -0,0 +1,90 @@ +package net.oschina.app.improve.utils; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.os.Build; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; + +import net.oschina.app.util.TDevice; +import net.oschina.app.util.TLog; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ +public class UiUtil { + private final static String TAG = "UiUtil"; + private final static String STATUS_BAR_DEF_PACKAGE = "android"; + private final static String STATUS_BAR_DEF_TYPE = "dimen"; + private final static String STATUS_BAR_NAME = "status_bar_height"; + private final static String STATUS_CLASS_NAME = "com.android.internal.R$dimen"; + private final static String STATUS_CLASS_FIELD = "status_bar_height"; + private static int STATUS_BAR_HEIGHT = 0; + + + public static synchronized int getStatusBarHeight(final Context context) { + if (STATUS_BAR_HEIGHT > 0) + return STATUS_BAR_HEIGHT; + + Resources resources = context.getResources(); + int resourceId = context.getResources(). + getIdentifier(STATUS_BAR_NAME, STATUS_BAR_DEF_TYPE, STATUS_BAR_DEF_PACKAGE); + if (resourceId > 0) { + STATUS_BAR_HEIGHT = context.getResources().getDimensionPixelSize(resourceId); + TLog.d(TAG, String.format("Get status bar height %s", STATUS_BAR_HEIGHT)); + } else { + try { + Class clazz = Class.forName(STATUS_CLASS_NAME); + Object object = clazz.newInstance(); + int height = Integer.parseInt(clazz.getField(STATUS_CLASS_FIELD) + .get(object).toString()); + STATUS_BAR_HEIGHT = resources.getDimensionPixelSize(height); + } catch (Exception e) { + e.printStackTrace(); + return (int) TDevice.dp2px(25); + } + } + return STATUS_BAR_HEIGHT; + } + + + public static boolean changeViewHeight(final View view, final int aimHeight) { + if (view.isInEditMode()) { + return false; + } + + if (view.getHeight() == aimHeight) { + return false; + } + + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (layoutParams == null) { + layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + aimHeight); + view.setLayoutParams(layoutParams); + } else { + layoutParams.height = aimHeight; + view.requestLayout(); + } + + return true; + } + + public static boolean isFullScreen(final Activity activity) { + return (activity.getWindow().getAttributes().flags & + WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0; + } + + public static boolean isTranslucentStatus(final Activity activity) { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && + (activity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0; + } + + static boolean isFitsSystemWindows(final Activity activity) { + return ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0). + getFitsSystemWindows(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/parser/MentionParser.java b/app/src/main/java/net/oschina/app/improve/utils/parser/MentionParser.java new file mode 100644 index 0000000000000000000000000000000000000000..d22979695ee4b1e6b1082f2c985e8f744bd78279 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/parser/MentionParser.java @@ -0,0 +1,38 @@ +package net.oschina.app.improve.utils.parser; + +import android.content.Context; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; + +import net.oschina.app.emoji.InputHelper; + +/** + * AT界面 + * Created by haibin + * on 2017/3/30. + */ + +public class MentionParser extends RichTextParser { + private static MentionParser mInstanse = new MentionParser(); + + public static MentionParser getInstance() { + return mInstanse; + } + + @Override + public Spannable parse(Context context, String text) { + String content; + if (TextUtils.isEmpty(text)) { + SpannableStringBuilder builder = new SpannableStringBuilder(); + return builder; + } + content = text.replaceAll("[\n\\s]+", " ").replaceAll(" ", " "); + Spannable spannable = parseOnlyAtUser(context, content); + spannable = parseOnlyTag(context, spannable); + spannable = parseOnlyLink(context, spannable); + spannable = parseOnlyTeamTask(context, spannable); + spannable = InputHelper.displayEmoji(context.getResources(), spannable); + return spannable; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/parser/RichTextParser.java b/app/src/main/java/net/oschina/app/improve/utils/parser/RichTextParser.java new file mode 100644 index 0000000000000000000000000000000000000000..652d55b30ae1bf049bd193e8094abc7950103ade --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/parser/RichTextParser.java @@ -0,0 +1,346 @@ +package net.oschina.app.improve.utils.parser; + +import android.content.Context; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.ClickableSpan; +import android.view.View; + +import net.oschina.app.improve.git.bean.Gist; +import net.oschina.app.improve.git.bean.Project; +import net.oschina.app.improve.git.bean.User; +import net.oschina.app.improve.git.detail.ProjectDetailActivity; +import net.oschina.app.improve.git.gist.detail.GistDetailActivity; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.util.UIHelper; +import net.sourceforge.pinyin4j.PinyinHelper; +import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType; +import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat; +import net.sourceforge.pinyin4j.format.HanyuPinyinToneType; +import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 富文本解析工具 + * Created by haibin + * on 2017/3/21. + */ +@SuppressWarnings("unused") +public abstract class RichTextParser { + + private static final Pattern PatternAtUserWithHtml = Pattern.compile( + "]+>(@([^@<>]+))" + ); + + static final Pattern PatternAtUser = Pattern.compile( + "@[^@\\s:]+" + ); + + // #Java# + private static final Pattern PatternSoftwareTagWithHtml = Pattern.compile( + "]*>(#[^#@<>\\s]+#)" + ); + static final Pattern PatternSoftwareTag = Pattern.compile( + "#([^#@<>\\s]+)#" + ); + + // @user links + @Deprecated + static final Pattern PatternAtUserAndLinks = Pattern.compile( + "]*>(@[^@<>\\s]+)" + + "|]*>([^<>]*)" + ); + + // git tag + private static final Pattern PatternGit = Pattern.compile( + "]*\'[^>]*data-project=\'([0-9]*)\'[^>]*>([^<>]*)" + ); + + // 代码片段 + private static final Pattern PatternGist = Pattern.compile( + "]+data-url=['\"]([^\\s]+)['\"][^>]+>([^>]+)" + ); + + // links + private static final Pattern PatternLinks = Pattern.compile( + "]*>([^<>]*)" + ); + + // team task + private static final Pattern PatternTeamTask = Pattern.compile( + "]*>([^<>]*)" + ); + + // html task + static final Pattern PatternHtml = Pattern.compile( + "<[^<>]+>([^<>]+)]+>" + ); + + /** + * 解析 + * + * @param context context + * @param content content + * @return Spannable + */ + public abstract Spannable parse(Context context, String content); + + /** + * @param sequence 文本 + * @param pattern 正则 + * @param usedGroupIndex 使用的组号 + * @param showGroupIndex 显示的组号 + * @param listener 点击回掉 + * @return 匹配后的文本 + */ + @SuppressWarnings("all") + private static Spannable assimilate(CharSequence sequence, + Pattern pattern, + int usedGroupIndex, + int showGroupIndex, + final OnClickListener listener) { + SpannableStringBuilder builder = new SpannableStringBuilder(sequence); + Matcher matcher; + while (true) { + matcher = pattern.matcher(builder.toString()); + if (matcher.find()) { + final String group0 = matcher.group(usedGroupIndex); + final String group1 = matcher.group(showGroupIndex); + builder.replace(matcher.start(), matcher.end(), group1); + ClickableSpan span = new ClickableSpan() { + @Override + public void onClick(View widget) { + listener.onClick(group0); + } + }; + builder.setSpan(span, matcher.start(), matcher.start() + group1.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + continue; + } + break; + } + return builder; + } + + /** + * 判断手机输入合法 + * + * @param phoneNumber 手机号码 + * @return true|false + */ + public static boolean machPhoneNum(CharSequence phoneNumber) { + String regex = "^[1][34578][0-9]\\d{8}$"; + return Pattern.matches(regex, phoneNumber); + } + + interface OnClickListener { + void onClick(String str); + } + + /** + * 字符串转化为拼音 + * 字符串中英文不转换为拼音 + * + * @param text 可能含有拼音的字符串 + * @param splitHead 每个中文转化为拼音后头部添加的分割符号 + * eg: "V好De","~"->"~V~hao~De" + * @return 转化为拼音后的字符串 + */ + public static String convertToPinyin(String text, String splitHead) { + if (TextUtils.isEmpty(text)) + return ""; + HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat(); + format.setCaseType(HanyuPinyinCaseType.LOWERCASE); + format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); + format.setVCharType(HanyuPinyinVCharType.WITH_U_UNICODE); + + char[] charArray = text.toCharArray(); + + StringBuilder sb = new StringBuilder(); + boolean canAdd = true; + for (char c : charArray) { + String temp = Character.toString(c); + if (temp.matches("[\u4E00-\u9FA5]+")) { + String py; + try { + String[] pys = PinyinHelper.toHanyuPinyinStringArray(c, format); + py = pys[0]; + } catch (Exception e) { + e.printStackTrace(); + py = " "; + } + sb.append(splitHead); + sb.append(py); + canAdd = true; + } else { + if (canAdd) { + sb.append(splitHead); + canAdd = false; + } + sb.append(temp); + } + } + return sb.toString().trim(); + } + + /** + * 格式化@xxx + * // http://my.oschina.net/u/user_id + * // http://my.oschina.net/user_ident + * + * @param context context + * @param content content + * @return Spannable + */ + static Spannable parseOnlyAtUser(final Context context, CharSequence content) { + SpannableStringBuilder builder = new SpannableStringBuilder(content); + Matcher matcher; + while (true) { + matcher = PatternAtUserWithHtml.matcher(builder.toString()); + if (matcher.find()) { + final String group0 = matcher.group(1); // ident 标识 如retrofit + final String group1 = matcher.group(2); // uid id + final String group2 = matcher.group(3); // @Nick + final String group3 = matcher.group(4); // Nick + builder.replace(matcher.start(), matcher.end(), group2); + long uid; + try { + uid = group1 == null ? 0 : Integer.valueOf(group1); + } catch (Exception e) { + uid = 0; + } + final long _uid = uid; + ClickableSpan span = new ClickableSpan() { + @Override + public void onClick(View widget) { + if (_uid > 0) { + OtherUserHomeActivity.show(context, _uid); + } else if (!TextUtils.isEmpty(group0)) { + OtherUserHomeActivity.show(context, 0, group0); + } else { + OtherUserHomeActivity.show(context, group3); + } + } + }; + builder.setSpan(span, matcher.start(), matcher.start() + group2.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + continue; + } + break; + } + return builder; + } + + + /** + * 格式化代码片段标签 + */ + static Spannable parseOnlyGist(final Context context, CharSequence content) { + SpannableStringBuilder builder = new SpannableStringBuilder(content); + Matcher matcher; + while (true) { + matcher = PatternGist.matcher(builder.toString()); + if (matcher.find()) { + final String group1 = matcher.group(1); + final String group2 = matcher.group(2); + final String group3 = matcher.group(3); + final String group4 = matcher.group(4); + builder.replace(matcher.start(), matcher.end(), group4); + ClickableSpan span = new ClickableSpan() { + @Override + public void onClick(View widget) { + Gist gist = new Gist(); + User user = new User(); + user.setName(group2); + gist.setOwner(user); + gist.setId(group4); + gist.setSummary(group2); + GistDetailActivity.show(context, gist); + } + }; + builder.setSpan(span, matcher.start(), matcher.start() + group4.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + continue; + } + break; + } + return builder; + } + + /** + * 格式化git项目标签 + */ + static Spannable parseOnlyGit(final Context context, CharSequence content) { + SpannableStringBuilder builder = new SpannableStringBuilder(content); + Matcher matcher; + while (true) { + matcher = PatternGit.matcher(builder.toString()); + if (matcher.find()) { + final String group0 = matcher.group(2); + final String group1 = matcher.group(3); + builder.replace(matcher.start(), matcher.end(), group1); + ClickableSpan span = new ClickableSpan() { + @Override + public void onClick(View widget) { + Project project = new Project(); + project.setId(Integer.parseInt(group0)); + ProjectDetailActivity.show(context, project); + } + }; + builder.setSpan(span, matcher.start(), matcher.start() + group1.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + continue; + } + break; + } + return builder; + } + + /** + * 格式化话题 + */ + static Spannable parseOnlyTag(final Context context, CharSequence content) { + return assimilate(content, PatternSoftwareTagWithHtml, 1, 2, new RichTextParser.OnClickListener() { + @Override + public void onClick(String str) { + UIHelper.showUrlRedirect(context, str); + } + }); + } + + /** + * 格式化链接 + */ + static Spannable parseOnlyLink(final Context context, CharSequence content) { + return assimilate(content, PatternLinks, 1, 2, new RichTextParser.OnClickListener() { + @Override + public void onClick(String str) { + UIHelper.showUrlRedirect(context, str); + } + }); + } + + public static boolean checkIsZH(String input) { + char[] charArray = input.toLowerCase().toCharArray(); + for (char c : charArray) { + String tempC = Character.toString(c); + if (tempC.matches("[\u4E00-\u9FA5]+")) { + return true; + } + } + return false; + } + + static Spannable parseOnlyTeamTask(final Context context, CharSequence content) { + return assimilate(content, PatternTeamTask, 1, 2, new OnClickListener() { + @Override + public void onClick(String str) { + UIHelper.openInternalBrowser(context, str); + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/parser/SearchParser.java b/app/src/main/java/net/oschina/app/improve/utils/parser/SearchParser.java new file mode 100644 index 0000000000000000000000000000000000000000..a02e71a03241cd8ce4c8eb3e2a8c9d2123a797f6 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/parser/SearchParser.java @@ -0,0 +1,64 @@ +package net.oschina.app.improve.utils.parser; + +import android.content.Context; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 搜索高亮显示 + * Created by huanghaibin on 2018/1/10. + */ + +public class SearchParser extends RichTextParser { + private static SearchParser mInstance = new SearchParser(); + + public static SearchParser getInstance() { + return mInstance; + } + + @Override + public Spannable parse(Context context, String content) { + return null; + } + + @SuppressWarnings("all") + public Spannable parse(String content, String keyword) { + SpannableStringBuilder builder = new SpannableStringBuilder(content); + if (true) {//去掉高亮 + return builder; + } + if (TextUtils.isEmpty(keyword)) { + return builder; + } + Pattern pattern = Pattern.compile(escape(keyword), Pattern.CASE_INSENSITIVE); + Matcher matcher; + matcher = pattern.matcher(builder.toString()); + while (matcher.find()) { + CharSequence s = builder.subSequence(matcher.start(), matcher.end()); + builder.replace(matcher.start(), matcher.end(), s); + ForegroundColorSpan span = new ForegroundColorSpan(0xffED5B5B); + builder.setSpan(span, matcher.start(), matcher.start() + s.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + return builder; + } + + /** + * 转义字符串 + */ + private static String escape(String keyword) { + String[] fbsArr = {"\\", "$", "(", ")", "*", "+", ".", "[", "]", "?", "^", "{", "}", "|"}; + for (String key : fbsArr) { + if (keyword.contains(key)) { + keyword = keyword.replace(key, "\\" + key); + } + } + return keyword; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/parser/StringParser.java b/app/src/main/java/net/oschina/app/improve/utils/parser/StringParser.java new file mode 100644 index 0000000000000000000000000000000000000000..264df55270b043fbc12e5d76e2af8b047bbf9f7d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/parser/StringParser.java @@ -0,0 +1,35 @@ +package net.oschina.app.improve.utils.parser; + +import android.content.Context; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; + +import net.oschina.app.emoji.InputHelper; + +/** + * 只显示文本,不需要富文本点击,如消息、私信列表 + * Created by haibin + * on 2017/3/22. + */ + +public class StringParser extends RichTextParser { + private static StringParser mInstance = new StringParser(); + + public static StringParser getInstance() { + return mInstance; + } + + @Override + public Spannable parse(Context context, String content) { + String text ; + if (TextUtils.isEmpty(content)) { + SpannableStringBuilder builder = new SpannableStringBuilder(); + text = "[图片]"; + builder.append(text); + return builder; + } + text = content.replaceAll("[\n\\s]+", " ").replaceAll("<[^<>]+>([^<>]*)]+>", "$1"); + return InputHelper.displayEmoji(context.getResources(), text); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/utils/parser/TweetParser.java b/app/src/main/java/net/oschina/app/improve/utils/parser/TweetParser.java new file mode 100644 index 0000000000000000000000000000000000000000..3a6eb56617e4ee8b1b04172700abeff810caa41f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/utils/parser/TweetParser.java @@ -0,0 +1,57 @@ +package net.oschina.app.improve.utils.parser; + +import android.content.Context; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; + +import net.oschina.app.emoji.InputHelper; +import net.oschina.app.util.HTMLUtil; + +import java.util.regex.Matcher; + +/** + * 动弹富文本解析 + * Created by haibin + * on 2017/3/21. + */ + +public class TweetParser extends RichTextParser { + private static TweetParser mInstance = new TweetParser(); + + public static TweetParser getInstance() { + return mInstance; + } + + @Override + public Spannable parse(Context context, String content) { + if (TextUtils.isEmpty(content)) + return null; + content = HTMLUtil.rollbackReplaceTag(content); + Spannable spannable = parseOnlyAtUser(context, content); + spannable = parseOnlyGist(context, spannable); + spannable = parseOnlyGit(context, spannable); + spannable = parseOnlyTag(context, spannable); + spannable = parseOnlyLink(context, spannable); + spannable = InputHelper.displayEmoji(context.getResources(), spannable); + return spannable; + } + + /** + * 清空HTML标签 + */ + public Spannable clearHtmlTag(CharSequence content) { + SpannableStringBuilder builder = new SpannableStringBuilder(content); + Matcher matcher; + while (true) { + matcher = PatternHtml.matcher(builder.toString()); + if (matcher.find()) { + String str = matcher.group(1); + builder.replace(matcher.start(), matcher.end(), str); + continue; + } + break; + } + return builder; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/AutoScrollView.java b/app/src/main/java/net/oschina/app/improve/widget/AutoScrollView.java new file mode 100644 index 0000000000000000000000000000000000000000..46bdd8c531b4ef75f70d557814ff62b0db3446be --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/AutoScrollView.java @@ -0,0 +1,28 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.support.v4.widget.NestedScrollView; +import android.util.AttributeSet; + +import net.oschina.app.improve.media.Util; + +/** + * 自适应的NestedScrollView + * Created by huanghaibin on 2017/11/16. + */ + +public class AutoScrollView extends NestedScrollView { + public AutoScrollView(Context context) { + this(context,null); + } + + public AutoScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + widthMeasureSpec = MeasureSpec.makeMeasureSpec(Util.getScreenWidth(getContext()), MeasureSpec.EXACTLY); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/AutoTextView.java b/app/src/main/java/net/oschina/app/improve/widget/AutoTextView.java new file mode 100644 index 0000000000000000000000000000000000000000..3464667e43d4dd3a6875c35917034f00af838ea9 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/AutoTextView.java @@ -0,0 +1,74 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.graphics.Paint; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatTextView; +import android.text.TextUtils; +import android.util.AttributeSet; + +/** + * 自动换行TextView + * Created by huanghaibin on 2018/06/06. + */ +public class AutoTextView extends AppCompatTextView { + + public AutoTextView(Context context) { + this(context, null); + } + + public AutoTextView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY + && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY + && getWidth() > 0 + && getHeight() > 0) { + String newText = autoSplitText(); + if (!TextUtils.isEmpty(newText)) { + setText(newText); + } + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + private String autoSplitText() { + final String rawText = getText().toString(); //原始文本 + final Paint tvPaint = getPaint(); //paint,包含字体等信息 + final float tvWidth = getWidth() - getPaddingLeft() - getPaddingRight(); //控件可用宽度 + //将原始文本按行拆分 + String[] rawTextLines = rawText.replaceAll("\r", "").split("\n"); + StringBuilder sbNewText = new StringBuilder(); + for (String rawTextLine : rawTextLines) { + if (tvPaint.measureText(rawTextLine) <= tvWidth) { + //如果整行宽度在控件可用宽度之内,就不处理了 + sbNewText.append(rawTextLine); + } else { + //如果整行宽度超过控件可用宽度,则按字符测量,在超过可用宽度的前一个字符处手动换行 + float lineWidth = 0; + for (int cnt = 0; cnt != rawTextLine.length(); ++cnt) { + char ch = rawTextLine.charAt(cnt); + lineWidth += tvPaint.measureText(String.valueOf(ch)); + if (lineWidth <= tvWidth) { + sbNewText.append(ch); + } else { + sbNewText.append("\n"); + lineWidth = 0; + --cnt; + } + } + } + sbNewText.append("\n"); + } + + //把结尾多余的\n去掉 + if (!rawText.endsWith("\n")) { + sbNewText.deleteCharAt(sbNewText.length() - 1); + } + + return sbNewText.toString(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/BottomDialog.java b/app/src/main/java/net/oschina/app/improve/widget/BottomDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..f152d421f3f9dea225b0d074d5e45fde7fcb2e06 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/BottomDialog.java @@ -0,0 +1,75 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.content.DialogInterface; +import android.support.annotation.NonNull; +import android.support.design.widget.BottomSheetBehavior; +import android.support.design.widget.BottomSheetDialog; +import android.support.design.widget.CoordinatorLayout; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; + +/** + * Created by thanatosx + * on 2016/12/23. + */ + +public class BottomDialog extends BottomSheetDialog { + + private BottomSheetBehavior behavior; + + public BottomDialog(@NonNull Context context, boolean isTranslucentStatus) { + super(context); + Window window = getWindow(); + if (window != null) { + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | + WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + if (isTranslucentStatus) + window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + } + + @Override + public void setContentView(View view) { + super.setContentView(view); + initialize(view); + setOnDismissListener(new OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + if (behavior != null) { + behavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + } + } + }); + } + + @Override + public void show() { + super.show(); + behavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } + + private void initialize(final View view) { + ViewGroup parent = (ViewGroup) view.getParent(); + CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) parent.getLayoutParams(); + behavior = (BottomSheetBehavior) params.getBehavior(); + if (behavior == null) + return; + behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { + @Override + public void onStateChanged(@NonNull View bottomSheet, int newState) { + if (newState == BottomSheetBehavior.STATE_HIDDEN) { + dismiss(); + behavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + //TDevice.hideSoftKeyboard(view); + } + } + + @Override + public void onSlide(@NonNull View bottomSheet, float slideOffset) { + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/BottomLineEditText.java b/app/src/main/java/net/oschina/app/improve/widget/BottomLineEditText.java new file mode 100644 index 0000000000000000000000000000000000000000..fd02b35b32167e5c9db4b2734fd62f9987599c36 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/BottomLineEditText.java @@ -0,0 +1,90 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.support.v7.widget.AppCompatEditText; +import android.text.Editable; +import android.text.InputFilter; +import android.text.TextWatcher; +import android.util.AttributeSet; + +import net.oschina.app.R; +import net.oschina.app.improve.media.Util; + +/** + * 自定义下划线 + * Created by huanghaibin on 2017/8/22. + */ +@SuppressWarnings("unused") +public class BottomLineEditText extends AppCompatEditText implements TextWatcher { + private Paint mPaint = new Paint(); + private int mMaxCount; + private boolean isShowCount; + private Paint mTextPaint = new Paint(); + + public BottomLineEditText(Context context) { + this(context, null); + } + + @SuppressWarnings("SuspiciousNameCombination") + public BottomLineEditText(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.BottomLineEditText); + setBackgroundColor(Color.TRANSPARENT); + int mLineColor = array.getColor(R.styleable.BottomLineEditText_line_color, 0xFF24cf5f); + float mLineHeight = array.getDimension(R.styleable.BottomLineEditText_line_height, 2); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeWidth(mLineHeight); + mPaint.setAntiAlias(true); + mPaint.setColor(mLineColor); + + mTextPaint.setStyle(Paint.Style.FILL); + mTextPaint.setStrokeWidth(mLineHeight); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextSize(Util.dipTopx(context, 12)); + mTextPaint.setColor(mLineColor); + + mMaxCount = array.getInt(R.styleable.BottomLineEditText_max_count, 16); + isShowCount = array.getBoolean(R.styleable.BottomLineEditText_is_show_count, true); + array.recycle(); + addTextChangedListener(this); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + float h = getHeight(); + int w = getWidth(); + int paddingRight = getPaddingRight(); + int length = getText().toString().length(); + canvas.drawLine(0, h - 1, w, h - 1, mPaint); + canvas.drawText(String.valueOf(mMaxCount - length), w - paddingRight, h - 30, mTextPaint); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void afterTextChanged(Editable s) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + public void setMaxCount(int mMaxCount) { + this.mMaxCount = mMaxCount; + setFilters(new InputFilter[]{new InputFilter.LengthFilter(mMaxCount)}); + } + + public void setShowCount(boolean showCount) { + isShowCount = showCount; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/BottomSheetBar.java b/app/src/main/java/net/oschina/app/improve/widget/BottomSheetBar.java new file mode 100644 index 0000000000000000000000000000000000000000..97743076b50dadeb4f5de1ef890018d6305b9505 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/BottomSheetBar.java @@ -0,0 +1,199 @@ +package net.oschina.app.improve.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.FrameLayout; +import android.widget.ImageButton; + +import net.oschina.app.R; +import net.oschina.app.improve.emoji.EmojiView; +import net.oschina.app.util.TDevice; + +/** + * 底部弹出评论框 + * Created by haibin + * on 2016/11/10. + *

    + * Changed by fei + * on 2016/11/17 + * + * @author Qiujuer + */ +@SuppressWarnings("unused") +public class BottomSheetBar { + + private View mRootView; + private RichEditText mEditText; + private ImageButton mAtView; + private ImageButton mFaceView; + private CheckBox mSyncToTweetView; + private Context mContext; + private Button mBtnCommit; + private BottomDialog mDialog; + private FrameLayout mFrameLayout; + private EmojiView mEmojiView; + + + private BottomSheetBar(Context context) { + this.mContext = context; + } + + @SuppressLint("InflateParams") + public static BottomSheetBar delegation(Context context) { + BottomSheetBar bar = new BottomSheetBar(context); + bar.mRootView = LayoutInflater.from(context).inflate(R.layout.layout_bottom_sheet_comment_bar, null, false); + bar.initView(); + return bar; + } + + private void initView() { + mFrameLayout = (FrameLayout) mRootView.findViewById(R.id.fl_face); + mEditText = (RichEditText) mRootView.findViewById(R.id.et_comment); + mAtView = (ImageButton) mRootView.findViewById(R.id.ib_mention); + mFaceView = (ImageButton) mRootView.findViewById(R.id.ib_face); + mFaceView.setVisibility(View.GONE); + mSyncToTweetView = (CheckBox) mRootView.findViewById(R.id.cb_sync); + if (mFaceView.getVisibility() == View.GONE) { + mSyncToTweetView.setText(R.string.send_tweet); + } + mBtnCommit = (Button) mRootView.findViewById(R.id.btn_comment); + mBtnCommit.setEnabled(false); + + mDialog = new BottomDialog(mContext, false); + mDialog.setContentView(mRootView); + + mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + TDevice.closeKeyboard(mEditText); + mFrameLayout.setVisibility(View.GONE); + } + }); + + mEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + mBtnCommit.setEnabled(s.length() > 0); + } + }); + } + + public void hideSyncAction() { + mSyncToTweetView.setVisibility(View.INVISIBLE); + mSyncToTweetView.setText(null); + } + + public void hideMentionAction() { + mAtView.setVisibility(View.INVISIBLE); + } + + /** + * 默认显示的 + */ + public void showSyncAction() { + mSyncToTweetView.setVisibility(View.VISIBLE); + mSyncToTweetView.setText(R.string.send_tweet); + } + + public void showEmoji() { + mSyncToTweetView.setText(R.string.tweet_publish_title); + mFaceView.setVisibility(View.VISIBLE); + mFaceView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mEmojiView == null) { + mEmojiView = new EmojiView(mContext, mEditText); + mFrameLayout.addView(mEmojiView); + } + mFrameLayout.setVisibility(View.VISIBLE); + mEmojiView.openPanel(); + TDevice.closeKeyboard(mEditText); + } + }); + + mEditText.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mFrameLayout.setVisibility(View.GONE); + } + }); + } + + public void show(String hint) { + mDialog.show(); + if (!"添加评论".equals(hint)) { + mEditText.setHint(hint + " "); + } + mRootView.postDelayed(new Runnable() { + @Override + public void run() { + TDevice.showSoftKeyboard(mEditText); + } + }, 50); + } + + public void dismiss() { + TDevice.closeKeyboard(mEditText); + mDialog.dismiss(); + } + + public void setMentionListener(View.OnClickListener listener) { + mAtView.setOnClickListener(listener); + } + + public void setFaceListener(View.OnClickListener listener) { + mFaceView.setOnClickListener(listener); + } + + public void setCommitListener(View.OnClickListener listener) { + mBtnCommit.setOnClickListener(listener); + } + + public void handleSelectFriendsResult(Intent data) { + String names[] = data.getStringArrayExtra("names"); + if (names != null && names.length > 0) { + String text = ""; + for (String n : names) { + text += "@" + n + " "; + } + mEditText.getText().insert(mEditText.getSelectionEnd(), text); + } + } + + public RichEditText getEditText() { + return mEditText; + } + + public String getCommentText() { + String str = mEditText.getText().toString(); + return TextUtils.isEmpty(str) ? "" : str.trim(); + } + + public Button getBtnCommit() { + return mBtnCommit; + } + + public boolean isSyncToTweet() { + return mSyncToTweetView != null && mSyncToTweetView.isChecked(); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/CommentShareView.java b/app/src/main/java/net/oschina/app/improve/widget/CommentShareView.java new file mode 100644 index 0000000000000000000000000000000000000000..3f590c6ac646d7bf5ffcffc5f6f0537ec6a39eb4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/CommentShareView.java @@ -0,0 +1,228 @@ +package net.oschina.app.improve.widget; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.os.Environment; +import android.support.annotation.Nullable; +import android.support.v4.widget.NestedScrollView; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.bean.comment.Comment; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.comment.CommentReferView; +import net.oschina.app.improve.comment.CommentsUtil; +import net.oschina.app.improve.dialog.ShareDialog; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.util.StringUtils; +import net.oschina.app.widget.TweetTextView; + +import java.io.File; + +import butterknife.Bind; +import butterknife.ButterKnife; + +/** + * 评论分享的View + * Created by haibin on 2017/4/17. + */ +@SuppressWarnings({"unused", "ResultOfMethodCallIgnored"}) +public class CommentShareView extends NestedScrollView implements Runnable { + private CommentShareAdapter mAdapter; + private ShareDialog mShareDialog; + private ProgressDialog mDialog; + private Bitmap mBitmap; + private boolean isShare; + + public CommentShareView(Context context) { + this(context, null); + } + + public CommentShareView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + LayoutInflater.from(context).inflate(R.layout.lay_comment_share_view, this, true); + RecyclerView mRecyclerComment = (RecyclerView) findViewById(R.id.rv_comment); + mRecyclerComment.setLayoutManager(new LinearLayoutManager(context)); + mAdapter = new CommentShareAdapter(context); + mRecyclerComment.setAdapter(mAdapter); + mShareDialog = new ShareDialog((Activity) context, -1, false); + mDialog = DialogHelper.getProgressDialog(context); + mDialog.setMessage("请稍候..."); + mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + if (isShare) + return; + if (mBitmap != null && !mBitmap.isRecycled()) { + mBitmap.recycle(); + } + removeCallbacks(CommentShareView.this); + } + }); + } + + public void init(String title, Comment comment) { + if (comment == null) + return; + setText(R.id.tv_title, title); + mAdapter.clear(); + mAdapter.addItem(comment); + } + + public void dismiss() { + isShare = false; + if (mDialog != null) + mDialog.dismiss(); + if (mShareDialog != null) + mShareDialog.dismiss(); + } + + @Override + public void run() { + isShare = true; + if (mDialog == null) + return; + mDialog.dismiss(); + mBitmap = getBitmap(); + mShareDialog.bitmap(mBitmap); + mShareDialog.show(); + } + + public void share() { + mDialog.show(); + if (mBitmap != null && !mBitmap.isRecycled()) { + mBitmap.recycle(); + } + postDelayed(this, 2000); + } + + private void setText(int viewId, String text) { + ((TextView) findViewById(viewId)).setText(text); + } + + private Bitmap getBitmap() { + return create(getChildAt(0)); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mBitmap != null && !mBitmap.isRecycled()) { + mBitmap.recycle(); + } + clearShareImage(); + } + + public static void clearShareImage() { + try { + String url = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + .getAbsolutePath() + File.separator + "开源中国/share/"; + File file = new File(url); + if (!file.exists()) + return; + File[] files = file.listFiles(); + if (files == null || files.length == 0) + return; + for (File f : file.listFiles()) { + f.delete(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static Bitmap create(View v) { + try { + int w = v.getWidth(); + int h = v.getHeight(); + Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565); + Canvas c = new Canvas(bmp); + c.drawColor(Color.WHITE); + v.layout(0, 0, w, h); + v.draw(c); + return bmp; + } catch (OutOfMemoryError error) { + error.printStackTrace(); + return null; + } + } + + static class CommentShareAdapter extends BaseRecyclerAdapter { + private RequestManager mLoader; + + CommentShareAdapter(Context context) { + super(context, NEITHER); + mLoader = Glide.with(context); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.item_list_comment_share, parent, false); + return new CommentHolder(view); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, Comment item, int position) { + ((CommentHolder) holder).addComment(item); + } + + static class CommentHolder extends RecyclerView.ViewHolder { + @Bind(R.id.iv_avatar) + PortraitView mIvAvatar; + + @Bind(R.id.tv_name) + TextView mName; + @Bind(R.id.tv_pub_date) + TextView mPubDate; + + @Bind(R.id.lay_refer) + CommentReferView mCommentReferView; + + @Bind(R.id.tv_content) + TweetTextView mTweetTextView; + + CommentHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + + @SuppressLint("DefaultLocale") + void addComment(final Comment comment) { + Author author = comment.getAuthor(); + mIvAvatar.setup(author); + + String name; + if (author == null || TextUtils.isEmpty(name = author.getName())) + name = mName.getResources().getString(R.string.martian_hint); + mName.setText(name); + mPubDate.setText(StringUtils.formatDayTime(comment.getPubDate())); + + mCommentReferView.addComment(comment); + if (comment.getRefer() == null || comment.getRefer().length == 0) { + CommentsUtil.formatHtml(mTweetTextView.getResources(), mTweetTextView, comment.getContent(), true, false); + } else { + CommentsUtil.formatHtml(mTweetTextView.getResources(), mTweetTextView, comment.getContent(), false, false); + } + + } + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/DetailAboutView.java b/app/src/main/java/net/oschina/app/improve/widget/DetailAboutView.java new file mode 100644 index 0000000000000000000000000000000000000000..cd110992a06109554059fb351bc1f7237f9e742f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/DetailAboutView.java @@ -0,0 +1,100 @@ +package net.oschina.app.improve.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.simple.About; +import net.oschina.app.util.UIHelper; + +import java.util.List; + +/** + * Created by JuQiu + * on 16/6/12. + */ + +public class DetailAboutView extends LinearLayout { + private int mDefaultType; + private LinearLayout mLayAbouts; + private TextView mTitle; + + public DetailAboutView(Context context) { + super(context); + init(); + } + + public DetailAboutView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public DetailAboutView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + setOrientation(VERTICAL); + LayoutInflater inflater = LayoutInflater.from(getContext()); + View inflate = inflater.inflate(R.layout.lay_detail_about_layout, this, true); + + mTitle = (TextView) inflate.findViewById(R.id.tv_blog_detail_about); + mLayAbouts = (LinearLayout) findViewById(R.id.lay_blog_detail_about); + } + + /** + * set title + * + * @param title string + */ + public void setTitle(String title) { + mTitle.setText(title); + } + + public void setAbout(List abouts, int defaultType) { + mLayAbouts.removeAllViews(); + this.mDefaultType = defaultType; + final LayoutInflater inflater = LayoutInflater.from(getContext()); + if (abouts != null && abouts.size() > 0) { + int size = abouts.size(); + for (final About about : abouts) { + if (about == null) + continue; + @SuppressLint("InflateParams") + View lay = inflater.inflate(R.layout.lay_blog_detail_about, null, false); + ((TextView) lay.findViewById(R.id.tv_title)).setText(about.getTitle()); + + View layInfo = lay.findViewById(R.id.lay_info_view_comment); + layInfo.findViewById(R.id.iv_info_view).setVisibility(GONE); + ((TextView) layInfo.findViewById(R.id.tv_info_view)).setVisibility(GONE);//setText(String.valueOf(about.getViewCount())); + ((TextView) layInfo.findViewById(R.id.tv_info_comment)).setText(String.valueOf(about.getCommentCount())); + + if (--size == 0) { + lay.findViewById(R.id.line).setVisibility(View.INVISIBLE); + } + + lay.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + int type = mDefaultType; + if (about.getType() != 0) { + type = about.getType(); + } + UIHelper.showDetail(v.getContext(), type, about.getId(), null); + } + }); + + mLayAbouts.addView(lay); + } + } else { + setVisibility(View.GONE); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/FragmentPagerAdapter.java b/app/src/main/java/net/oschina/app/improve/widget/FragmentPagerAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..9c6e15d625c5ac13775a87b5a49db1c4f423a61c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/FragmentPagerAdapter.java @@ -0,0 +1,119 @@ +package net.oschina.app.improve.widget; + +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.PagerAdapter; +import android.view.View; +import android.view.ViewGroup; + +import net.oschina.app.util.TLog; + +/** + * 我们不希望维持Fragment的状态,因为可能活动的Tab会很多,所以既不能用 + * {@link android.support.v4.app.FragmentPagerAdapter}, 因为它通过{@link FragmentTransaction#add(Fragment, String)}, + * {@link FragmentTransaction#attach(Fragment)}和{@link FragmentTransaction#detach(Fragment)}的方式, + * 状态由{@link Fragment}自己维护,也不能用{@link android.support.v4.app.FragmentStatePagerAdapter}, 因为 + * 它通过{@link FragmentTransaction#add(Fragment, String)}和{@link FragmentTransaction#remove(Fragment)} + * 的方式,有两个集合保存加入的{@link Fragment}以及它们的状态 + *

    + *

    Created by thanatosx on 2016/11/9. + */ +@SuppressWarnings("all") +public abstract class FragmentPagerAdapter extends PagerAdapter { + + private final FragmentManager mFragmentManager; + private FragmentTransaction mCurTransaction; + private Fragment mCurrentPrimaryItem; + + public FragmentPagerAdapter(FragmentManager fm) { + mFragmentManager = fm; + } + + /** + * Return the fragment associated with a specified position + * + * @param position the position + * @return {@link Fragment} + */ + public abstract Fragment getItem(int position); + + @Override + public void startUpdate(ViewGroup container) { + if (container.getId() == View.NO_ID) { + throw new IllegalStateException("ViewPager with adapter " + this + + " requires a view id"); + } + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + + Fragment fragment = getItem(position); + TLog.i("oschina", "Adding fragment item #" + position + ": f=" + fragment); + fragment.setMenuVisibility(false); + fragment.setUserVisibleHint(false); + mCurTransaction.add(container.getId(), fragment, + makeFragmentName(container.getId(), getItemId(position))); + return fragment; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment) object; + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + TLog.i("oschina", "Removing fragment #" + position + ": f=" + fragment + + " v=" + fragment.getView()); + mCurTransaction.remove(fragment); + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment) object; + if (fragment != mCurrentPrimaryItem) { + if (mCurrentPrimaryItem != null) { + mCurrentPrimaryItem.setMenuVisibility(false); + mCurrentPrimaryItem.setUserVisibleHint(false); + } + if (fragment != null) { + fragment.setMenuVisibility(true); + fragment.setUserVisibleHint(true); + } + mCurrentPrimaryItem = fragment; + } + } + + @Override + public void finishUpdate(ViewGroup container) { + /*if (mCurTransaction != null) { + mCurTransaction.commitNowAllowingStateLoss(); + mCurTransaction = null; + }*/ + } + + public void commitUpdate() { + if (mCurTransaction != null) { + mCurTransaction.commitNowAllowingStateLoss(); + mCurTransaction = null; + } + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return ((Fragment) object).getView() == view; + } + + protected String makeFragmentName(int viewId, long id) { + return "android:switcher:" + viewId + ":" + id; + } + + protected long getItemId(int position) { + return position; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/IdentityView.java b/app/src/main/java/net/oschina/app/improve/widget/IdentityView.java new file mode 100644 index 0000000000000000000000000000000000000000..f1fed72c72d91d83762820352af272d4008e3f3f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/IdentityView.java @@ -0,0 +1,132 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.GradientDrawable; +import android.support.v7.widget.AppCompatTextView; +import android.util.AttributeSet; +import android.view.Gravity; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.util.TDevice; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ +public class IdentityView extends AppCompatTextView { + private static final int STROKE_SIZE = 2; + private int mColor = 0xff24CF5F; + private boolean mWipeOffBorder = false; + private Author.Identity mIdentity; + private GradientDrawable mDrawable; + + public IdentityView(Context context) { + super(context); + init(null, 0); + } + + public IdentityView(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0); + } + + public IdentityView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr); + } + + private void init(AttributeSet attrs, int defStyleAttr) { + Context context = getContext(); + + if (attrs != null) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.IdentityView, defStyleAttr, 0); + mColor = a.getColor(R.styleable.IdentityView_oscColor, mColor); + mWipeOffBorder = a.getBoolean(R.styleable.IdentityView_oscWipeOffBorder, mWipeOffBorder); + a.recycle(); + } + + setVisibility(GONE); + setTextSize(10); + setGravity(Gravity.CENTER); + setSingleLine(true); + setLines(1); + setColor(mColor); + setText(R.string.identity_officialMember); + + final int padding = (int) TDevice.dipToPx(getResources(), 2); + setPadding(padding + padding, padding, padding + padding, padding); + + if (isInEditMode()) { + Author.Identity identity = new Author.Identity(); + identity.officialMember = true; + setup(identity); + } + } + + public void setColor(int color) { + mColor = color; + final GradientDrawable drawable = mDrawable; + if (drawable != null) { + drawable.setStroke(STROKE_SIZE, color); + } + setTextColor(color); + invalidate(); + } + + public void setup(Author author) { + if (author == null) + setup((Author.Identity) null); + else + setup(author.getIdentity()); + } + + public void setup(Author.Identity identity) { + this.mIdentity = identity; + + if (identity == null) { + setVisibility(GONE); + return; + } + + setVisibility(identity.officialMember ? VISIBLE : GONE); + setText(R.string.identity_officialMember); + initBorder(); + } + + private void initBorder() { +// if (mWipeOffBorder || mIdentity == null || !mIdentity.officialMember) { +// mDrawable = null; +// setBackground(null); +// return; +// } + + if (mDrawable == null) { + float radius = 4f; + + GradientDrawable gradientDrawable = new GradientDrawable(); + gradientDrawable.setGradientType(GradientDrawable.LINEAR_GRADIENT); + gradientDrawable.setShape(GradientDrawable.RECTANGLE); + gradientDrawable.setDither(true); + gradientDrawable.setStroke(STROKE_SIZE, mColor); + gradientDrawable.setCornerRadius(radius); + + mDrawable = gradientDrawable; + } else { + mDrawable.setStroke(STROKE_SIZE, mColor); + } + + setBackground(mDrawable); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + final GradientDrawable drawable = mDrawable; + if (drawable != null) { + drawable.setCornerRadius(4); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/Keyboard.java b/app/src/main/java/net/oschina/app/improve/widget/Keyboard.java new file mode 100644 index 0000000000000000000000000000000000000000..0e1d4ddcd09c0359bfb820cc24f981de883a8bea --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/Keyboard.java @@ -0,0 +1,44 @@ +package net.oschina.app.improve.widget; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +import static android.content.Context.CLIPBOARD_SERVICE; + +/** + * 键盘辅助类 + * Created by huanghaibin on 2017/7/11. + */ +@SuppressWarnings("unused") +public final class Keyboard { + + public static int KEYBOARD_HEIGHT = 0; + + public static void openKeyboard(View view) { + view.setFocusable(true); + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + assert imm != null; + imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); + } + + + public static void closeKeyboard(View view) { + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + assert imm != null; + imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN); + } + + public static void copy(Context context, String text) { + if (text == null) + return; + ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE); + ClipData clipData = ClipData.newPlainText("text", text); + assert clipboardManager != null; + clipboardManager.setPrimaryClip(clipData); + } + +} + diff --git a/app/src/main/java/net/oschina/app/improve/widget/OSCTextView.java b/app/src/main/java/net/oschina/app/improve/widget/OSCTextView.java new file mode 100644 index 0000000000000000000000000000000000000000..b65ca565ee9d5a2139d21da45d9ea8299b4e7b9c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/OSCTextView.java @@ -0,0 +1,67 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Typeface; +import android.support.v7.widget.AppCompatTextView; +import android.text.TextUtils; +import android.util.AttributeSet; + +import net.oschina.app.R; + +/** + * 自定义字体 + * Created by huanghaibin on 2017/11/27. + */ + +public class OSCTextView extends AppCompatTextView { + private static final int FONT_TYPE_SYSTEM = 0; + + private static final int FONT_TYPE_LIGHT = 1; + + private static final int FONT_TYPE_MEDIUM = 2; + + private static final int FONT_TYPE_REGULAR = 3; + + private int mFontType = 0; + + public OSCTextView(Context context) { + this(context, null); + } + + public OSCTextView(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.OSCTextView); + mFontType = array.getInt(R.styleable.OSCTextView_font_type, FONT_TYPE_SYSTEM); + array.recycle(); + init(context); + } + + private void init(Context context) { + String path = getFont(); + if (TextUtils.isEmpty(path)) + return; + Typeface type = Typeface.createFromAsset(context.getAssets(), path); + setTypeface(type); + } + + private String getFont() { + String typeface = null; + switch (mFontType) { + case FONT_TYPE_SYSTEM: + break; + case FONT_TYPE_LIGHT: + typeface = "fonts/NotoSansSC-Light.otf"; + break; + case FONT_TYPE_MEDIUM: + typeface = "fonts/NotoSansSC-Medium.otf"; + break; + case FONT_TYPE_REGULAR: + typeface = "fonts/NotoSansSC-Regular.otf"; + break; + default: + break; + } + return typeface; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/OSCWebView.java b/app/src/main/java/net/oschina/app/improve/widget/OSCWebView.java new file mode 100644 index 0000000000000000000000000000000000000000..3b5bdcbf48a7ea629cd7618938ebc1ca74f07d9e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/OSCWebView.java @@ -0,0 +1,338 @@ +package net.oschina.app.improve.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.net.http.SslError; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.webkit.SslErrorHandler; +import android.webkit.WebChromeClient; +import android.webkit.WebResourceError; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import net.oschina.app.improve.main.synthesize.web.Rule; + +/** + * 浏览器,视频、图片都支持 + * Created by huanghaibin on 2017/10/27. + */ +@SuppressWarnings("unused") +public class OSCWebView extends WebView { + + private OnFinishListener mOnFinishFinish; + private OnLoadedHtmlListener mHTMLListener; + private OnImageClickListener mImageClickListener; + private OnVideoClickListener mVideoClickListener; + private boolean isFinish; + private Rule mRule; + private boolean isRemove; + + public OSCWebView(Context context) { + this(context, null); + } + + public OSCWebView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + @SuppressLint("SetJavaScriptEnabled") + private void init() { + setWebChromeClient(new WebChromeClient() { + @Override + public void onReceivedTitle(WebView view, String title) { + super.onReceivedTitle(view, title); + //hideAD(); + if (mOnFinishFinish != null) { + mOnFinishFinish.onReceivedTitle(title); + } + } + + @Override + public void onProgressChanged(WebView view, int newProgress) { + super.onProgressChanged(view, newProgress); + if (!isRemove) { + isRemove = true; + startLoadRule(); + } + if (mOnFinishFinish != null) { + mOnFinishFinish.onProgressChange(newProgress); + } + } + + }); + + setWebViewClient(new WebViewClient() { + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + handler.proceed(); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + return super.shouldOverrideUrlLoading(view, request); + + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + exeExpandRule(); + postDelayed(new Runnable() { + @Override + public void run() { + isFinish = true; + } + }, 2000); + if (mOnFinishFinish != null) { + mOnFinishFinish.onFinish(); + } + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + super.onPageStarted(view, url, favicon); + } + + @Override + public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { + super.onReceivedError(view, request, error); + if (mOnFinishFinish != null) { + mOnFinishFinish.onError(); + } + } + + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + return super.shouldInterceptRequest(view, request); + } + }); + + getSettings().setDomStorageEnabled(true); + getSettings().setJavaScriptCanOpenWindowsAutomatically(true); + getSettings().setJavaScriptEnabled(true); + addJavascriptInterface(new JavascriptInterface(), "mark"); + + setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + OSCWebView webView = (OSCWebView) v; + HitTestResult result = webView.getHitTestResult(); + if (null == result) + return false; + + int type = result.getType(); + if (type == WebView.HitTestResult.UNKNOWN_TYPE) + return false; + + if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) { + // let TextViewhandles context menu + return false; + } + + + // Setup custom handlingdepending on the type + switch (type) { + case WebView.HitTestResult.PHONE_TYPE: + // 处理拨号 + break; + case WebView.HitTestResult.EMAIL_TYPE: + // 处理Email + break; + case WebView.HitTestResult.GEO_TYPE: + // TODO + break; + case WebView.HitTestResult.SRC_ANCHOR_TYPE: + // 超链接 + break; + case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: + case WebView.HitTestResult.IMAGE_TYPE: + // 处理长按图片的菜单项 + break; + default: + break; + } + + return false; + } + }); + } + + @SuppressWarnings("all") + public void setUserAgent(String ua) { + getSettings().setUserAgentString(ua); + } + + /** + * 去除广告规则 + */ + private void exeRemoveRule() { + if (mRule == null) + return; + String[] rules = mRule.getRemoveRules(); + if (rules == null || rules.length == 0) + return; + for (String rule : rules) { + evaluateJavascript(rule, null); + } + } + + /** + * 展开全文规则 + */ + private void exeExpandRule() { + if (mRule == null) + return; + String[] rules = mRule.getExpandRules(); + if (rules == null || rules.length == 0) + return; + for (String rule : rules) { + evaluateJavascript(rule, null); + } + } + + private int delay = 1; + + public void setRule(Rule mRule) { + this.mRule = mRule; + } + + private void startLoadRule() { + if (Build.VERSION.SDK_INT <= 22) { + delay = 50; + } else { + delay = 20; + } + + if (mRule == null || + mRule.getRemoveRules() == null || + mRule.getRemoveRules().length == 0) + return; + postDelayed(new Runnable() { + @Override + public void run() { + if (isFinish) + return; + exeRemoveRule(); + if (!isFinish) { + postDelayed(this, delay); + } + } + }, 0); + } + + + @SuppressWarnings("deprecation") + public void onDestroy() { + isFinish = true; + ViewParent parent = getParent(); + if (parent != null) { + ((ViewGroup) parent).removeView(this); + } + stopLoading(); + getSettings().setJavaScriptEnabled(false); + clearHistory(); + clearView(); + removeAllViews(); + mOnFinishFinish = null; + mImageClickListener = null; + mVideoClickListener = null; + destroy(); + } + + public boolean hasRule() { + return mRule != null; + } + + public void getHtml(OnLoadedHtmlListener listener) { + this.mHTMLListener = listener; + loadUrl("javascript:window.mark.showHTML(''+document.getElementsByTagName('html')[0].innerHTML+'');"); + } + + private void addJavaScript() { + loadUrl("javascript:(function(){" + + "var objs = document.getElementsByTagName(\"img\"); " + + "for(var i=0;i= Build.VERSION_CODES.JELLY_BEAN_MR1) { + addJavascriptInterface(new OnWebViewImageListener() { + @Override + @JavascriptInterface + public void showImagePreview(String bigImageUrl) { + if (bigImageUrl != null && !StringUtils.isEmpty(bigImageUrl)) { + ImageGalleryActivity.show(getContext(), bigImageUrl); + } + } + }, "mWebViewImageListener"); + } + } + + public void loadDetailDataAsync(final String content) { + Context context = getContext(); + if (context != null && context instanceof Activity) { + final Activity activity = (Activity) context; + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + final String body = setupWebContent(content, true, true, useShareCss, ""); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + //loadData(body, "text/html; charset=UTF-8", null); + loadDataWithBaseURL("", body, "text/html", "UTF-8", ""); + } + }); + } + }); + } else { + TLog.e(OWebView.class.getName(), "loadDetailDataAsync error, the Context isn't ok"); + } + } + + + public void setUseShareCss(boolean useShareCss) { + this.useShareCss = useShareCss; + } + + public void loadDetailDataAsync(final String content, Runnable finishCallback) { + this.setWebViewClient(new OWebClient(finishCallback)); + Context context = getContext(); + if (context != null && context instanceof Activity) { + final Activity activity = (Activity) context; + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + final String body = setupWebContent(content, true, true, useShareCss, ""); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + //loadData(body, "text/html; charset=UTF-8", null); + loadDataWithBaseURL("", body, "text/html", "UTF-8", ""); + } + }); + } + }); + } else { + TLog.e(OWebView.class.getName(), "loadDetailDataAsync error, the Context isn't ok"); + } + } + + + public void loadTweetDataAsync(final String content, Runnable finishCallback) { + this.setWebViewClient(new OWebClient(finishCallback)); + Context context = getContext(); + if (context != null && context instanceof Activity) { + final Activity activity = (Activity) context; + AppOperator.runOnThread(new Runnable() { + @Override + public void run() { + final String body = setupWebContent(content, ""); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + //loadData(body, "text/html; charset=UTF-8", null); + loadDataWithBaseURL("", body, "text/html", "UTF-8", ""); + } + }); + } + }); + } else { + TLog.e(OWebView.class.getName(), "loadDetailDataAsync error, the Context isn't ok"); + } + } + + @Override + public void destroy() { + setWebViewClient(null); + + WebSettings settings = getSettings(); + settings.setJavaScriptEnabled(false); + + removeJavascriptInterface("mWebViewImageListener"); + removeAllViewsInLayout(); + + removeAllViews(); + //clearCache(true); + + super.destroy(); + } + + private static String setupWebContent(String content, String style) { + if (TextUtils.isEmpty(content) || TextUtils.isEmpty(content.trim())) + return ""; + if (AppContext.get(AppConfig.KEY_LOAD_IMAGE, true) + || TDevice.isWifiOpen()) { + Pattern pattern = Pattern.compile("]+src\\s*=\\s*[\"\']([^\"\']*)[\"\'](\\s*data-url\\s*=\\s*[\"\']([^\"\']*)[\"\'])*"); + Matcher matcher = pattern.matcher(content); + while (matcher.find()) { + String snippet = String.format( + "]*)\\s*>", ""); + } + return String.format( + "" + + "" + + "" + + "" + + "" + + "" + + "

    " + + "%s" + + "
    " + + "" + + "" + , style == null ? "" : style, content); + } + + private static String setupWebContent(String content, boolean isShowHighlight, boolean isShowImagePreview, boolean isUseShareCss, String css) { + if (TextUtils.isEmpty(content) || TextUtils.isEmpty(content.trim())) + return ""; + + // 读取用户设置:是否加载文章图片--默认有wifi下始终加载图片 + if (AppContext.get(AppConfig.KEY_LOAD_IMAGE, true) + || TDevice.isWifiOpen()) { + //content = content.replaceAll("<([u|o])l.*>", "<$1l style=\"padding-left:20px\">"); + // 过滤掉 img标签的width,height属性 + content = content.replaceAll("(]*?)\\s+width\\s*=\\s*\\S+", "$1"); + content = content.replaceAll("(]*?)\\s+height\\s*=\\s*\\S+", "$1"); + + // 添加点击图片放大支持 + if (isShowImagePreview) { + // TODO 用一个正则就搞定?? + content = content.replaceAll("]+src=\"([^\"\'\\s]+)\"[^>]*>", + ""); + content = content.replaceAll( + "]*href=[\"\']([^\"\']+)[\"\'][^<>]*>\\s*]*/>\\s*", + ""); + } + } else { + // 过滤掉 img标签 + content = content.replaceAll("<\\s*img\\s+([^>]*)\\s*/>", ""); + } + + // 过滤table的内部属性 + content = content.replaceAll("(]*?)\\s+border\\s*=\\s*\\S+", "$1"); + content = content.replaceAll("(]*?)\\s+cellspacing\\s*=\\s*\\S+", "$1"); + content = content.replaceAll("(]*?)\\s+cellpadding\\s*=\\s*\\S+", "$1"); + + + return String.format( + "" + + "" + + "" + + (isShowHighlight ? "" : "") + + (isShowHighlight ? "" : "") + + String.format("", isUseShareCss ? "common_detail_share.css" : "common_detail.css") + + "%s" + + "" + + "" + + "
    " + + "%s" + + "
    " + + (isShowHighlight ? "" : "") + + (isShowHighlight ? "" : "") + + (isShowHighlight ? "" : "") + + "" + + "" + , (css == null ? "" : css), content); + } + + private static class OWebClient extends WebViewClient implements Runnable { + private Runnable mFinishCallback; + private boolean mDone = false; + + OWebClient(Runnable finishCallback) { + super(); + mFinishCallback = finishCallback; + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + super.onPageStarted(view, url, favicon); + mDone = false; + // 当webview加载2秒后强制回馈完成 + view.postDelayed(this, 2800); + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + run(); + } + + @Override + public synchronized void run() { + if (!mDone) { + mDone = true; + if (mFinishCallback != null) mFinishCallback.run(); + } + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + //UIHelper.showUrlRedirect(view.getContext(), url); + UIFormat.show(view.getContext(), url); + return true; + } + + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + // TODO + handler.cancel(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/PortraitView.java b/app/src/main/java/net/oschina/app/improve/widget/PortraitView.java new file mode 100644 index 0000000000000000000000000000000000000000..ae2d3dabc658488580d4566af802c859cef89a19 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/PortraitView.java @@ -0,0 +1,211 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.text.TextPaint; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.SizeReadyCallback; +import com.bumptech.glide.request.target.Target; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.util.TLog; + +import de.hdodenhof.circleimageview.CircleImageView; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ +public class PortraitView extends CircleImageView { + private static final String TAG = PortraitView.class.getSimpleName(); + private Author author; + + public PortraitView(Context context) { + super(context); + init(); + } + + public PortraitView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public PortraitView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (author == null || (author.getId() == 0 && TextUtils.isEmpty(author.getName()))) + return; + if (author.getId() == 0) + return; + OtherUserHomeActivity.show(getContext(), author); + } + }); + } + + public static void setup(ImageView imageView, Author author) { + if (imageView != null && imageView instanceof PortraitView) { + ((PortraitView) imageView).setup(author); + } else { + TLog.error("PortraitView con't setup with:" + + (imageView == null ? "null" : imageView.getClass().getSimpleName())); + } + } + + public void setup(Author author) { + if (author == null) + return; + + this.author = author; + load(author.getName(), author.getPortrait()); + } + + public void setup(final long id, final String name, String path) { + if (id == 0 && TextUtils.isEmpty(name) && TextUtils.isEmpty(path)) { + return; + } + + Author author = new Author(); + author.setId(id); + author.setName(name); + author.setPortrait(path); + setup(author); + } + + + private void load(final String name, String path) { + final Context context = getContext(); + if (context == null) + return; + + if (path == null) { + path = ""; + } else { + String pathTmp = path.toLowerCase(); + if (pathTmp.contains("www.oschina.net/img/portrait".toLowerCase()) + || pathTmp.contains("secure.gravatar.com/avatar".toLowerCase())) { + log("replace path:" + path); + path = ""; + } + } + + log("load path:" + path); + + Glide.with(context) + .load(path) + .asBitmap() + .placeholder(R.color.black_alpha_48) + .error(R.mipmap.widget_default_face) + .listener(new RequestListener() { + @Override + public boolean onException(Exception e, String model, Target target, boolean isFirstResource) { + target.getSize(new SizeReadyCallback() { + @Override + public void onSizeReady(int width, int height) { + final String firstChar = (TextUtils.isEmpty(name) ? "-" : name.trim().substring(0, 1)).toUpperCase(); + Bitmap bitmap = buildSrcFromName(firstChar, width, height); + setScaleType(ScaleType.CENTER_CROP); + setImageBitmap(bitmap); + } + }); + return true; + } + + @Override + public boolean onResourceReady(Bitmap resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { + return false; + } + }) + .into(this); + } + + @SuppressWarnings("ResourceAsColor") + private Bitmap buildSrcFromName(final String firstChar, int w, int h) { + if (w == Target.SIZE_ORIGINAL || w <= 0) + w = 80; + if (h == Target.SIZE_ORIGINAL || h <= 0) + h = 80; + + final int size = Math.max(Math.min(Math.min(w, h), 220), 64); + final float fontSize = size * 0.4f; + log("firstChar:" + firstChar + " size:" + size + " fontSize:" + fontSize); + + Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565); + + TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + paint.setAntiAlias(true); + paint.setDither(true); + paint.setColor(Color.WHITE); + paint.setTextAlign(Paint.Align.CENTER); + paint.setTextSize(fontSize); + paint.setTypeface(Typeface.SANS_SERIF); + + // check ASCII + final int charNum = Character.getNumericValue(firstChar.charAt(0)); + if (charNum > 0 && charNum < 177) { + Typeface typeface = getFont(getContext(), "Numans-Regular.otf"); + if (typeface != null) + paint.setTypeface(typeface); + } + + Rect rect = new Rect(); + paint.getTextBounds(firstChar, 0, 1, rect); + int fontHeight = rect.height(); + log(rect.toString()); + + int fontHalfH = fontHeight >> 1; + int centerX = bitmap.getWidth() >> 1; + int centerY = bitmap.getHeight() >> 1; + + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(getBackgroundColor(firstChar)); + canvas.drawText(firstChar, centerX, centerY + fontHalfH, paint); + + return bitmap; + } + + private static int getBackgroundColor(String firstChar) { + int len = COLORS.length; + int index = firstChar.charAt(0) - 64; + int colorIndex = Math.abs(index) % len; + return COLORS[colorIndex]; + } + + public static Typeface getFont(Context context, String fontFile) { + String fontPath = "fonts/" + fontFile; + + try { + return Typeface.createFromAsset(context.getAssets(), fontPath); + } catch (Exception var4) { + log("Font file at " + fontPath + " cannot be found or the file is not a valid font file."); + return null; + } + } + + private static void log(String args) { + TLog.i(TAG, args); + } + + static final int[] COLORS = new int[]{ + 0xFF1abc9c, 0xFF2ecc71, 0xFF3498db, 0xFF9b59b6, 0xFF34495e, 0xFF16a085, 0xFF27ae60, 0xFF2980b9, 0xFF8e44ad, 0xFF2c3e50, + 0xFFf1c40f, 0xFFe67e22, 0xFFe74c3c, 0xFFeca0f1, 0xFF95a5a6, 0xFFf39c12, 0xFFd35400, 0xFFc0392b, 0xFFbdc3c7, 0xFF7f8c8d + }; +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/RadarView.java b/app/src/main/java/net/oschina/app/improve/widget/RadarView.java new file mode 100644 index 0000000000000000000000000000000000000000..c717a1fdde39d66f4e1cf04aece1b33123cf7954 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/RadarView.java @@ -0,0 +1,30 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; + +/** + * 技能雷达图 + * Created by huanghaibin on 2018/4/9. + */ + +public class RadarView extends View { + private Paint mPathPaint = new Paint(); + + public RadarView(Context context) { + super(context); + } + + public RadarView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onDraw(Canvas canvas) { + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/RecentContactsView.java b/app/src/main/java/net/oschina/app/improve/widget/RecentContactsView.java new file mode 100644 index 0000000000000000000000000000000000000000..4c51250b888932c7693fcd97e8b28dc7b9e1f279 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/RecentContactsView.java @@ -0,0 +1,186 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.bumptech.glide.Glide; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.simple.Author; +import net.oschina.app.improve.user.activities.OtherUserHomeActivity; +import net.oschina.app.improve.user.helper.ContactsCacheManager; +import net.oschina.app.util.ImageLoader; +import net.oschina.app.util.TDevice; + +import java.util.ArrayList; +import java.util.List; + +import de.hdodenhof.circleimageview.CircleImageView; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ + +public class RecentContactsView extends LinearLayout implements View.OnClickListener, + ContactsCacheManager.SelectedTrigger { + private final List models = new ArrayList<>(); + private OnSelectedChangeListener listener; + + public RecentContactsView(Context context) { + super(context); + init(); + } + + public RecentContactsView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public RecentContactsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + setOrientation(VERTICAL); + RecyclerView.LayoutParams params = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + //params.topMargin = (int) TDevice.dipToPx(getResources(), 2); + params.bottomMargin = (int) TDevice.dipToPx(getResources(), 16); + setLayoutParams(params); + + List authors = ContactsCacheManager.getRecentCache(getContext()); + if (authors.size() == 0) + setVisibility(GONE); + else { + LayoutInflater inflater = LayoutInflater.from(getContext()); + inflater.inflate(R.layout.lay_recent_contacts, this, true); + + for (Author author : authors) { + Model model = new Model(author); + models.add(model); + View view = createView(inflater, model); + addView(view, view.getLayoutParams()); + refreshView(model); + } + } + } + + private View createView(LayoutInflater inflater, Model model) { + View view = inflater.inflate(R.layout.activity_item_select_friend, this, false); + view.setOnClickListener(this); + // 双向绑定 + model.tag = view; + view.setTag(model); + return view; + } + + private void refreshView(Model model) { + View root = model.tag; + final Author author = model.author; + + CircleImageView portrait = (CircleImageView) root.findViewById(R.id.iv_portrait); + TextView name = (TextView) root.findViewById(R.id.tv_name); + ImageView isSelected = (ImageView) root.findViewById(R.id.iv_select); + View line = root.findViewById(R.id.line); + + ImageLoader.loadImage(Glide.with(getContext()), portrait, author.getPortrait(), R.mipmap.widget_default_face); + + portrait.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + OtherUserHomeActivity.show(v.getContext(), author.getId()); + } + }); + name.setText(author.getName()); + + if (model.isSelected) { + isSelected.setVisibility(View.VISIBLE); + } else { + isSelected.setVisibility(View.INVISIBLE); + } + line.setVisibility(View.GONE); + } + + public boolean hasData() { + return models.size() > 0; + } + + public void setSelected(Author author, boolean selected) { + if (!hasData() || author == null) + return; + + for (Model model : models) { + if (model.author.getId() == author.getId()) { + model.isSelected = selected; + refreshView(model); + } + } + } + + public void setListener(OnSelectedChangeListener listener) { + this.listener = listener; + } + + @Override + public void onClick(View v) { + if (listener == null || + v.getTag() == null || + !(v.getTag() instanceof Model)) + return; + + Model model = (Model) v.getTag(); + listener.tryTriggerSelected(model, this); + } + + @Override + public void trigger(Model model, boolean selected) { + model.isSelected = selected; + refreshView(model); + } + + @Override + public void trigger(Author author, boolean selected) { + if (author == null) + return; + for (Model model : models) { + if (model.author.getId() == author.getId()) { + trigger(model, selected); + return; + } + } + } + + public static class Model { + Model(Author author) { + this.author = author; + } + + public Author author; + boolean isSelected = false; + View tag; + + @Override + public String toString() { + return "Model{" + + "author=" + author + + ", isSelected=" + isSelected + + ", tag=" + tag + + '}'; + } + } + + + public interface OnSelectedChangeListener { + void tryTriggerSelected(Model t, ContactsCacheManager.SelectedTrigger trigger); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/RecyclerRefreshLayout.java b/app/src/main/java/net/oschina/app/improve/widget/RecyclerRefreshLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..8880c3067cea2e5bb5b1ac7dcde8154190d3d862 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/RecyclerRefreshLayout.java @@ -0,0 +1,248 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; + +import net.oschina.app.R; + +/** + * 下拉刷新上拉加载控件,目前适用于RecyclerView + * Created by huanghaibin on 16-5-3. + */ +public class RecyclerRefreshLayout extends SwipeRefreshLayout implements SwipeRefreshLayout.OnRefreshListener { + private RecyclerView mRecycleView; + + private int mTouchSlop; + + private SuperRefreshLayoutListener listener; + + private boolean mIsOnLoading = false; + + private boolean mCanLoadMore = true; + + private boolean mHasMore = true; + + private int mYDown; + + private int mLastY; + + private int mBottomCount = 1; + + public RecyclerRefreshLayout(Context context) { + this(context, null); + } + + public RecyclerRefreshLayout(Context context, AttributeSet attrs) { + super(context, attrs); + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + setOnRefreshListener(this); + } + + @Override + public void onRefresh() { + if (listener != null && !mIsOnLoading) { + listener.onRefreshing(); + } else + setRefreshing(false); + } + + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + // 初始化ListView对象 + if (mRecycleView == null) { + getRecycleView(); + } + } + + /** + * 获取RecyclerView,后续支持AbsListView + */ + private void getRecycleView() { + if (getChildCount() > 0) { + View childView = getChildAt(0); + if (!(childView instanceof RecyclerView)) { + childView = findViewById(R.id.recyclerView); + } + if (childView != null && childView instanceof RecyclerView) { + mRecycleView = (RecyclerView) childView; + mRecycleView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + if (canLoad() && mCanLoadMore) { + loadData(); + } else if (isNextScrollBottom() && listener != null && mCanLoadMore && !mIsOnLoading) { + listener.onScrollToBottom(); + } + } + }); + } + } + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + final int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + mYDown = (int) event.getRawY(); + break; + case MotionEvent.ACTION_MOVE: + mLastY = (int) event.getRawY(); + break; + default: + break; + } + return super.dispatchTouchEvent(event); + } + + /** + * 是否可以加载更多, 条件是到了最底部 + * + * @return isCanLoad + */ + private boolean canLoad() { + return isScrollBottom() && !mIsOnLoading && isPullUp() && mHasMore; + } + + /** + * 如果到了最底部,而且是上拉操作.那么执行onLoad方法 + */ + private void loadData() { + if (listener != null) { + setOnLoading(true); + listener.onLoadMore(); + } + } + + /** + * 是否是上拉操作 + * + * @return isPullUp + */ + private boolean isPullUp() { + return (mYDown - mLastY) >= mTouchSlop; + } + + /** + * 设置正在加载 + * + * @param loading 设置正在加载 + */ + public void setOnLoading(boolean loading) { + mIsOnLoading = loading; + if (!mIsOnLoading) { + mYDown = 0; + mLastY = 0; + } + } + + public boolean isLoding() { + return mIsOnLoading; + } + + @SuppressWarnings("all") + public void setBottomCount(int mBottomCount) { + this.mBottomCount = mBottomCount; + } + + /** + * 判断是否到了最底部 + */ + private boolean isScrollBottom() { + return (mRecycleView != null && mRecycleView.getAdapter() != null) + && getLastVisiblePosition() == (mRecycleView.getAdapter().getItemCount() - mBottomCount); + } + + /** + * 判断是否到了最底部 + */ + private boolean isNextScrollBottom() { + return (mRecycleView != null && mRecycleView.getAdapter() != null) + && getLastVisiblePosition() == (mRecycleView.getAdapter().getItemCount() - 1); + } + + /** + * 加载结束记得调用 + */ + public void onComplete() { + setOnLoading(false); + setRefreshing(false); + mHasMore = true; + } + + /** + * 是否可加载更多 + * + * @param mCanLoadMore 是否可加载更多 + */ + public void setCanLoadMore(boolean mCanLoadMore) { + this.mCanLoadMore = mCanLoadMore; + } + + /** + * 获取RecyclerView可见的最后一项 + * + * @return 可见的最后一项position + */ + public int getLastVisiblePosition() { + int position; + if (mRecycleView.getLayoutManager() instanceof LinearLayoutManager) { + position = ((LinearLayoutManager) mRecycleView.getLayoutManager()).findLastVisibleItemPosition(); + } else if (mRecycleView.getLayoutManager() instanceof GridLayoutManager) { + position = ((GridLayoutManager) mRecycleView.getLayoutManager()).findLastVisibleItemPosition(); + } else if (mRecycleView.getLayoutManager() instanceof StaggeredGridLayoutManager) { + StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) mRecycleView.getLayoutManager(); + int[] lastPositions = layoutManager.findLastVisibleItemPositions(new int[layoutManager.getSpanCount()]); + position = getMaxPosition(lastPositions); + } else { + position = mRecycleView.getLayoutManager().getItemCount() - 1; + } + return position; + } + + /** + * 获得最大的位置 + * + * @param positions 获得最大的位置 + * @return 获得最大的位置 + */ + private int getMaxPosition(int[] positions) { + int maxPosition = Integer.MIN_VALUE; + for (int position : positions) { + maxPosition = Math.max(maxPosition, position); + } + return maxPosition; + } + + /** + * 添加加载和刷新 + * + * @param listener add the listener for SuperRefreshLayout + */ + public void setSuperRefreshLayoutListener(SuperRefreshLayoutListener listener) { + this.listener = listener; + } + + public interface SuperRefreshLayoutListener { + void onRefreshing(); + + void onLoadMore(); + + void onScrollToBottom(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/RichEditText.java b/app/src/main/java/net/oschina/app/improve/widget/RichEditText.java new file mode 100644 index 0000000000000000000000000000000000000000..82a78168062005b881dc03b344712a5f999db821 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/RichEditText.java @@ -0,0 +1,487 @@ +package net.oschina.app.improve.widget; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.v7.widget.AppCompatEditText; +import android.text.Editable; +import android.text.Selection; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputConnectionWrapper; + +import net.oschina.common.adapter.TextWatcherAdapter; +import net.oschina.common.utils.Logger; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * 一个简单的富文本编辑器 + * 实现了@(AT)和##的Tag匹配功能, + * 具有Tag删除判断,和光标定位判断;预防用户胡乱篡改 + * + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ +@SuppressWarnings("all") +public class RichEditText extends AppCompatEditText { + public static final String MATCH_MENTION = "@([^@^\\s^:^,^;^','^';'^>^<]{1,})";//@([^@^\\s^:]{1,})([\\s\\:\\,\\;]{0,1})");//@.+?[\\s:] + public static final String MATCH_TOPIC = "#.+?#"; + public static boolean DEBUG = false; + private static final String TAG = RichEditText.class.getName(); + private final RichEditText.TagSpanTextWatcher mTagSpanTextWatcher = new RichEditText.TagSpanTextWatcher(); + private RichEditText.OnKeyArrivedListener mOnKeyArrivedListener; + + public RichEditText(Context context) { + super(context); + init(); + } + + public RichEditText(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public RichEditText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + addTextChangedListener(mTagSpanTextWatcher); + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + return new RichEditText.ZanyInputConnection(super.onCreateInputConnection(outAttrs), true); + } + + @Override + public void setText(CharSequence text, BufferType type) { + Spannable spannable = new SpannableString(text); + spannable = matchMention(spannable); + spannable = matchTopic(spannable); + super.setText(spannable, type); + } + + @Override + protected void onSelectionChanged(int selStart, int selEnd) { + log("onSelectionChanged:" + selStart + " " + selEnd); + Editable message = getText(); + + if (selStart == selEnd) { + RichEditText.TagSpan[] list = message.getSpans(selStart - 1, selStart, RichEditText.TagSpan.class); + if (list.length > 0) { + // Get first tag + RichEditText.TagSpan span = list[0]; + int spanStart = message.getSpanStart(span); + int spanEnd = message.getSpanEnd(span); + log("onSelectionChanged#Yes:" + spanStart + " " + spanEnd); + // Check index + if (Math.abs(selStart - spanStart) > Math.abs(selStart - spanEnd)) { + Selection.setSelection(message, spanEnd); + replaceCacheTagSpan(message, span, false); + return; + } else { + Selection.setSelection(message, spanStart); + } + } + } else { + RichEditText.TagSpan[] list = message.getSpans(selStart, selEnd, RichEditText.TagSpan.class); + if (list.length == 0) + return; + int start = selStart; + int end = selEnd; + for (RichEditText.TagSpan span : list) { + int spanStart = message.getSpanStart(span); + int spanEnd = message.getSpanEnd(span); + + if (spanStart < start) + start = spanStart; + + if (spanEnd > end) + end = spanEnd; + } + if (start != selStart || end != selEnd) { + Selection.setSelection(message, start, end); + log("onSelectionChanged#No:" + start + " " + end); + } + } + + replaceCacheTagSpan(message, null, false); + } + + @Override + public boolean onTextContextMenuItem(int id) { + // Handle the paste option + if (id == android.R.id.paste) { + // Handle to the clipboard service. + ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + // Handle the data. + if (clipboard.hasPrimaryClip()) { + ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0); + if (item != null) { + // Gets the clipboard date to string and do trim + String paste = item.coerceToText(getContext()).toString().trim(); + // Check need space + if (mTagSpanTextWatcher != null && mTagSpanTextWatcher.checkCommit(paste)) + paste = " " + paste; + // Clear add span + Spannable spannablePaste = new SpannableString(paste); + spannablePaste = matchMention(spannablePaste); + spannablePaste = matchTopic(spannablePaste); + getText().replace(getSelectionStart(), getSelectionEnd(), spannablePaste); + return true; + } + } + } + // Call super + return super.onTextContextMenuItem(id); + } + + public void setOnKeyArrivedListener(RichEditText.OnKeyArrivedListener listener) { + mOnKeyArrivedListener = listener; + } + + protected boolean callToMention() { + RichEditText.OnKeyArrivedListener listener = mOnKeyArrivedListener; + return listener == null || listener.onMentionKeyArrived(this); + } + + protected boolean callToTopic() { + RichEditText.OnKeyArrivedListener listener = mOnKeyArrivedListener; + return listener == null || listener.onTopicKeyArrived(this); + } + + private void replaceCacheTagSpan(Editable message, RichEditText.TagSpan span, boolean targetDelState) { + if (mTagSpanTextWatcher != null) { + mTagSpanTextWatcher.replaceSpan(message, span, targetDelState); + } + } + + private String filterDirty(String str) { + return str.replace("#", "").replace("@", "").replace(" ", ""); + } + + private void replaceLastChar(@NonNull String chr, SpannableString spannable) { + Editable msg = getText(); + int selStart = getSelectionStart(); + int selEnd = getSelectionEnd(); + + int selStartBefore = selStart - 1; + if (selStart == selEnd && selStart > 0 + && chr.equals(msg.subSequence(selStartBefore, selEnd).toString()) + && msg.getSpans(selStartBefore, selEnd, RichEditText.TagSpan.class).length == 0) { + selStart = selStartBefore; + } + + msg.replace(selStart >= 0 ? selStart : 0, selEnd >= 0 ? selEnd : 0, spannable); + } + + /** + * 添加提到字符串 + * + * @param mentions 提及的人,不含@ + */ + @SuppressWarnings("unused") + public void appendMention(String... mentions) { + if (mentions == null || mentions.length == 0) + return; + + String mentionStr = ""; + + for (String mention : mentions) { + if (mention == null || TextUtils.isEmpty(mention = mention.trim()) + || TextUtils.isEmpty(mention = filterDirty(mention))) + continue; + mentionStr += String.format("@%s ", mention); + } + if (TextUtils.isEmpty(mentionStr)) + return; + + SpannableString spannable = new SpannableString(mentionStr); + RichEditText.matchMention(spannable); + + replaceLastChar("@", spannable); + } + + /** + * 添加话题字符串 + * + * @param topics 话题,不含# + */ + @SuppressWarnings("unused") + public void appendTopic(String... topics) { + if (topics == null || topics.length == 0) + return; + + String topicStr = ""; + + for (String topic : topics) { + if (topic == null || TextUtils.isEmpty(topic = topic.trim()) + || TextUtils.isEmpty(topic = filterDirty(topic))) + continue; + topicStr += String.format("#%s# ", topic); + } + if (TextUtils.isEmpty(topicStr)) + return; + + SpannableString spannable = new SpannableString(topicStr); + RichEditText.matchTopic(spannable); + + replaceLastChar("#", spannable); + } + + + private class TagSpanTextWatcher extends TextWatcherAdapter { + private RichEditText.TagSpan willDelSpan; + + void replaceSpan(Editable message, RichEditText.TagSpan span, boolean targetDelState) { + if (span != null) + span.changeRemoveState(targetDelState, message); + + if (willDelSpan != span) { + // When different + RichEditText.TagSpan cacheSpan = willDelSpan; + if (cacheSpan != null) { + cacheSpan.changeRemoveState(false, message); + } + willDelSpan = span; + } + } + + boolean checkKeyDel() { + int selStart = getSelectionStart(); + int selEnd = getSelectionEnd(); + Editable message = getText(); + log("TagSpanTextWatcher#checkKeyDel:" + selStart + " " + selEnd); + if (selStart == selEnd) { + int start = selStart - 1; + int count = 1; + + start = start < 0 ? 0 : start; + + int end = start + count; + RichEditText.TagSpan[] list = message.getSpans(start, end, RichEditText.TagSpan.class); + + if (list.length > 0) { + // Only get first + final RichEditText.TagSpan span = list[0]; + final RichEditText.TagSpan cacheSpan = willDelSpan; + + if (span == cacheSpan) { + if (span.willRemove) + return true; + else { + span.changeRemoveState(true, message); + return false; + } + } + } + } + // Replace cache tag to null + replaceSpan(message, null, false); + return true; + } + + boolean checkCommit(CharSequence s) { + if (willDelSpan != null) { + willDelSpan.willRemove = false; + willDelSpan = null; + return s != null && s.length() > 0 && !" ".equals(s.subSequence(0, 1)); + } + return false; + } + + @Override + public void afterTextChanged(Editable s) { + final RichEditText.TagSpan span = willDelSpan; + log("TagSpanTextWatcher#willRemove#span:" + (span == null ? "null" : span.toString())); + if (span != null && span.willRemove) { + int start = s.getSpanStart(span); + int end = s.getSpanEnd(span); + + // Remove the span + s.removeSpan(span); + + // Remove the remaining emoticon text. + if (start != end) { + s.delete(start, end); + } + } + } + } + + public interface OnKeyArrivedListener { + boolean onMentionKeyArrived(RichEditText editText); + + boolean onTopicKeyArrived(RichEditText text); + } + + public static Spannable matchMention(Spannable spannable) { + String text = spannable.toString(); + + Pattern pattern = Pattern.compile(MATCH_MENTION); + Matcher matcher = pattern.matcher(text); + + while (matcher.find()) { + String str = matcher.group(); + int matcherStart = matcher.start(); + int matcherEnd = matcher.end(); + spannable.setSpan(new RichEditText.TagSpan(str), matcherStart, matcherEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + log("matchMention:" + str + " " + matcherStart + " " + matcherEnd); + } + + return spannable; + } + + public static Spannable matchTopic(Spannable spannable) { + String text = spannable.toString(); + + Pattern pattern = Pattern.compile(MATCH_TOPIC); + Matcher matcher = pattern.matcher(text); + + while (matcher.find()) { + String str = matcher.group(); + int matcherStart = matcher.start(); + int matcherEnd = matcher.end(); + spannable.setSpan(new RichEditText.TagSpan(str), matcherStart, matcherEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + log("matchTopic:" + str + " " + matcherStart + " " + matcherEnd); + } + + return spannable; + } + + private static void log(String msg) { + if (DEBUG) + Logger.e(TAG, msg); + } + + @SuppressWarnings("WeakerAccess") + public static class TagSpan extends ForegroundColorSpan implements Parcelable { + private String value; + public boolean willRemove; + + public TagSpan(String value) { + super(0xFF24cf5f); + this.value = value; + } + + public TagSpan(Parcel src) { + super(src); + value = src.readString(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public RichEditText.TagSpan createFromParcel(Parcel in) { + return new RichEditText.TagSpan(in); + } + + @Override + public RichEditText.TagSpan[] newArray(int size) { + return new RichEditText.TagSpan[size]; + } + }; + + @Override + public void updateDrawState(TextPaint ds) { + //log("TagSpan:updateDrawState:" + isPreDeleteState); + ds.setFakeBoldText(true); + if (willRemove) { + ds.setColor(0xFFFFFFFF); + ds.bgColor = 0xFF24cf5f; + } else { + super.updateDrawState(ds); + } + } + + void changeRemoveState(boolean willRemove, Editable message) { + if (this.willRemove == willRemove) + return; + this.willRemove = willRemove; + int cacheSpanStart = message.getSpanStart(this); + int cacheSpanEnd = message.getSpanEnd(this); + if (cacheSpanStart >= 0 && cacheSpanEnd >= cacheSpanStart) { + message.setSpan(this, cacheSpanStart, cacheSpanEnd, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + public String getValue() { + return value; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(value); + } + + @Override + public String toString() { + return "TagSpan{" + + "value='" + value + '\'' + + ", willRemove=" + willRemove + + '}'; + } + } + + + private class ZanyInputConnection extends InputConnectionWrapper { + + ZanyInputConnection(InputConnection target, boolean mutable) { + super(target, mutable); + } + + @Override + public boolean sendKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getKeyCode() == KeyEvent.KEYCODE_DEL) { + if (!RichEditText.this.mTagSpanTextWatcher.checkKeyDel()) + return false; + } + return super.sendKeyEvent(event); + } + + @Override + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + // magic: in latest Android, deleteSurroundingText(1, 0) will be called for backspace + if (beforeLength == 1 && afterLength == 0) { + // backspace + return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) + && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); + } + + return super.deleteSurroundingText(beforeLength, afterLength); + } + + @Override + public boolean commitText(CharSequence text, int newCursorPosition) { + return super.commitText(text, newCursorPosition); + } + + @Override + public boolean setComposingText(CharSequence text, int newCursorPosition) { + return super.setComposingText(text, newCursorPosition); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/RippleIntroView.java b/app/src/main/java/net/oschina/app/improve/widget/RippleIntroView.java new file mode 100644 index 0000000000000000000000000000000000000000..9f920b92e7f2270dd5f4fbe87dee77763e74350b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/RippleIntroView.java @@ -0,0 +1,127 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.util.AttributeSet; +import android.view.View; +import android.widget.RelativeLayout; + +/** + * 涟漪View + * Created by thanatosx on 2016/11/15. + */ + +public class RippleIntroView extends RelativeLayout implements Runnable { + + private int mMaxRadius = 70; + private int mInterval = 20; + private int count = 0; + + private Bitmap mCacheBitmap; + private Paint mRipplePaint; + private Paint mCirclePaint; + private Path mArcPath; + + public RippleIntroView(Context context) { + this(context, null); + } + + public RippleIntroView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public RippleIntroView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + mRipplePaint = new Paint(); + mRipplePaint.setAntiAlias(true); + mRipplePaint.setStyle(Paint.Style.STROKE); + mRipplePaint.setColor(Color.WHITE); + mRipplePaint.setStrokeWidth(2.f); + + mCirclePaint = new Paint(); + mCirclePaint.setAntiAlias(true); + mCirclePaint.setStyle(Paint.Style.FILL); + mCirclePaint.setColor(Color.WHITE); + + mArcPath = new Path(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (mCacheBitmap != null) { + mCacheBitmap.recycle(); + mCacheBitmap = null; + } + } + + @Override + protected void onDraw(Canvas canvas) { + // 我知道直接getChildAt(int)很挫,但是我就是要这么简单粗暴! + View mPlusChild = getChildAt(0); + View mRefsChild = getChildAt(1); + if (mPlusChild == null || mRefsChild == null) return; + + final int pw = mPlusChild.getWidth(); + final int ph = mPlusChild.getHeight(); + + final int fw = mRefsChild.getWidth(); + final int fh = mRefsChild.getHeight(); + + if (pw == 0 || ph == 0) return; + + final float px = mPlusChild.getX() + pw / 2; + final float py = mPlusChild.getY() + ph / 2; + final float fx = mRefsChild.getX(); + final float fy = mRefsChild.getY(); + final int rw = pw / 2; + final int rh = ph / 2; + + if (mCacheBitmap == null) { + mCacheBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); + Canvas cv = new Canvas(mCacheBitmap); + super.onDraw(cv); + + mArcPath.reset(); + mArcPath.moveTo(px, py + rh + mInterval); + mArcPath.quadTo(px, fy - mInterval, fx + fw * 0.618f, fy - mInterval); + mRipplePaint.setAlpha(255); + cv.drawPath(mArcPath, mRipplePaint); + cv.drawCircle(px, py + rh + mInterval, 6, mCirclePaint); + } + canvas.drawBitmap(mCacheBitmap, 0, 0, mCirclePaint); + + int save = canvas.save(); + for (int step = count; step <= mMaxRadius; step += mInterval) { + mRipplePaint.setAlpha(255 * (mMaxRadius - step) / mMaxRadius); + canvas.drawCircle(px, py, (float) (rw + step), mRipplePaint); + } + canvas.restoreToCount(save); + postDelayed(this, 80); + } + + @Override + public void run() { + removeCallbacks(this); + count += 2; + count %= mInterval; + invalidate(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mCacheBitmap != null) { + mCacheBitmap.recycle(); + mCacheBitmap = null; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/ScreenView.java b/app/src/main/java/net/oschina/app/improve/widget/ScreenView.java new file mode 100644 index 0000000000000000000000000000000000000000..af713343fe844170032e9e9dd1d32d34bdff4e1a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/ScreenView.java @@ -0,0 +1,40 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.graphics.Rect; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; + +import net.oschina.app.improve.media.Util; + +/** + * 判断View是否在屏幕范围内 + * Created by haibin + * on 2017/1/13. + */ + +public class ScreenView extends View { + public ScreenView(Context context) { + super(context); + } + + public ScreenView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public boolean isViewInScreen() { + + int screenWidth = Util.getScreenWidth(getContext()); + + int screenHeight = Util.getScreenHeight(getContext()); + + Rect rect = new Rect(0, 0, screenWidth, screenHeight); + + int[] location = new int[2]; + + getLocationInWindow(location); + + return getLocalVisibleRect(rect); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/SimplexToast.java b/app/src/main/java/net/oschina/app/improve/widget/SimplexToast.java new file mode 100644 index 0000000000000000000000000000000000000000..fa302ba257be63af0d52cc2d6a7ef8a0d5c0d634 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/SimplexToast.java @@ -0,0 +1,72 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.view.Gravity; +import android.widget.Toast; + +/** + * 以后请用这个吐司,谢谢!!! + *

    + *

    + * {@link Toast}的创建都是要inflate一个layout, findViewById之类的 + * 将一个吐司单例化,并且作防止频繁点击的处理。 + *

    + *

    + * Created by thanatosx on 2016/11/15. + */ +@SuppressWarnings("all") +public class SimplexToast { + + private static Toast mToast; + private static long nextTimeMillis; + private static int yOffset; + + private SimplexToast(Context context) { + + } + + public static Toast init(Context context) { + if (context == null) { + throw new IllegalArgumentException("Context should not be null!!!"); + } + if (mToast == null) { + mToast = Toast.makeText(context, null, Toast.LENGTH_SHORT); + yOffset = mToast.getYOffset(); + } + mToast.setDuration(Toast.LENGTH_SHORT); + mToast.setGravity(Gravity.BOTTOM, 0, yOffset); + mToast.setMargin(0, 0); + return mToast; + } + + public static void show(String content) { + show(content, Toast.LENGTH_SHORT); + } + + public static void show(String content, int duration) { + show(null, content, Gravity.BOTTOM, duration); + } + + public static void show(Context context, int rid) { + show(context, context.getResources().getString(rid)); + } + + public static void show(Context context, String content) { + show(context, content, Gravity.BOTTOM); + } + + public static void show(Context context, String content, int gravity) { + show(context, content, gravity, Toast.LENGTH_SHORT); + } + + public static void show(Context context, String content, int gravity, int duration) { + long current = System.currentTimeMillis(); + //if (current < nextTimeMillis) return; + if (mToast == null) init(context.getApplicationContext()); + mToast.setText(content); + mToast.setDuration(duration); + mToast.setGravity(gravity, 0, yOffset); + nextTimeMillis = current + (duration == Toast.LENGTH_LONG ? 3500 : 2000); + mToast.show(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/SmoothLayout.java b/app/src/main/java/net/oschina/app/improve/widget/SmoothLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..a769c6ada9bebf8520c5349d15aabde1fa108e78 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/SmoothLayout.java @@ -0,0 +1,146 @@ +package net.oschina.app.improve.widget; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.graphics.Rect; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.FrameLayout; + +import java.lang.reflect.Field; + +/** + * 平滑弹出键盘 + * Created by huanghaibin on 2017/12/18. + */ + +public class SmoothLayout extends FrameLayout { + + public static int TYPE_FONT = 1; + public static int TYPE_LETTER_PAGER = 2; + public static int TYPE_PHOTO = 3; + public static int TYPE_LIST = 4; + public static int TYPE_TIME = 5; + + private int mType; + + private EditText mEditText; + private boolean isKeyboardOpen = false;//键盘是否打开 + + public SmoothLayout(@NonNull Context context) { + this(context, null); + } + + public SmoothLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public void bindEditText(EditText editText) { + this.mEditText = editText; + } + + private void init() { + final Activity activity = (Activity) getContext(); + final Window window = activity.getWindow(); + final View rootView = window.getDecorView(); + window.getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + Rect r = new Rect(); + rootView.getWindowVisibleDisplayFrame(r); + int screenHeight = rootView.getHeight(); + int heightDiff = screenHeight - (r.bottom - r.top); + int statusBarHeight = 0; + if (heightDiff > 200) { + try { + @SuppressLint("PrivateApi") Class c = Class.forName("com.android.internal.R$dimen"); + Object obj = c.newInstance(); + Field field = c.getField("status_bar_height"); + int x = Integer.parseInt(field.get(obj).toString()); + statusBarHeight = getResources().getDimensionPixelSize(x); + } catch (Exception e) { + e.printStackTrace(); + } + int h = heightDiff - statusBarHeight; + if (Keyboard.KEYBOARD_HEIGHT < h) { + Keyboard.KEYBOARD_HEIGHT = h; + getLayoutParams().height = Keyboard.KEYBOARD_HEIGHT; + } + } + } + }); + } + + + /** + * 显示或者隐藏面板 + */ + public void showOrClosePanel(int type) { + setAdjustNothing(); + if (getVisibility() == View.VISIBLE) { + if (isKeyboardOpen) { + closeKeyboard(); + } else { + if (this.mType != type && mType != 0) { + this.mType = type; + return; + } + this.mType = type; + setVisibility(View.GONE); + closeKeyboard(); + } + } else { + this.mType = type; + closeKeyboard(); + setVisibility(View.VISIBLE); + } + } + + public void openKeyboard() { + isKeyboardOpen = true; + Keyboard.openKeyboard(mEditText); + } + + public void closeKeyboard() { + isKeyboardOpen = false; + Keyboard.closeKeyboard(mEditText); + } + + public void setAdjustResize() { + + if (getVisibility() == View.VISIBLE) { + setAdjustNothing(); + postDelayed(new Runnable() { + @Override + public void run() { + isKeyboardOpen = true; + setVisibility(GONE); + setResize(); + } + }, 500); + return; + } + isKeyboardOpen = true; + setResize(); + } + + private void setResize() { + Window window = ((Activity) getContext()).getWindow(); + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + } + + public void setAdjustNothing() { + Window window = ((Activity) getContext()).getWindow(); + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/SolarSystemView.java b/app/src/main/java/net/oschina/app/improve/widget/SolarSystemView.java new file mode 100644 index 0000000000000000000000000000000000000000..d03befe5e80cef320f27f92cebaf15ea8dd981fd --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/SolarSystemView.java @@ -0,0 +1,315 @@ +package net.oschina.app.improve.widget; + +import android.animation.FloatEvaluator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RadialGradient; +import android.graphics.Shader; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; + +import java.util.ArrayList; +import java.util.List; + +/** + * 类太阳系星球转动 + * Created by thanatosx on 16/7/14. + */ +public class SolarSystemView extends View implements Runnable { + + public static final int FLUSH_RATE = 40; + public static final int FLUSH_RATE_LIMITATION = 16; + + private int mFlushRate = FLUSH_RATE; + private int paintCount; + private float pivotX; + private float pivotY; + private Paint mTrackPaint; + private Paint mPlanetPaint; + private Paint mBackgroundPaint; + private List planets; + private Bitmap mCacheBitmap; + + private ValueAnimator mAccelerateAnimator; + private ValueAnimator mDecelerateAnimator; + + public SolarSystemView(Context context) { + this(context, null); + } + + public SolarSystemView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SolarSystemView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + planets = new ArrayList<>(); + + mTrackPaint = new Paint(); + mTrackPaint.setStyle(Paint.Style.STROKE); + mTrackPaint.setAntiAlias(true); + + mPlanetPaint = new Paint(); + mPlanetPaint.setStyle(Paint.Style.FILL); + mPlanetPaint.setAntiAlias(true); + } + + public void setPivotPoint(float x, float y) { + pivotX = x; + pivotY = y; + paintCount = 0; + prepare(); + } + + public void addPlanets(List planets) { + this.planets.addAll(planets); + } + + public void addPlanets(Planet planet) { + planets.add(planet); + } + + public void clear() { + planets.clear(); + } + + public synchronized void prepare() { + if (planets.size() == 0) return; + + if (mCacheBitmap != null) { + mCacheBitmap.recycle(); + mCacheBitmap = null; + } + int w = getWidth(); + int h = getHeight(); + if (w == 0 || h == 0) + return; + mCacheBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(mCacheBitmap); + if (getBackground() != null) { + getBackground().draw(canvas); + } + if (mBackgroundPaint != null && mBackgroundPaint.getShader() != null) { + canvas.drawRect(0, 0, getWidth(), getHeight(), mBackgroundPaint); + } + for (Planet planet : planets) { + mTrackPaint.setStrokeWidth(planet.getTrackWidth()); + mTrackPaint.setColor(planet.getTrackColor()); + canvas.drawCircle(pivotX, pivotY, planet.getRadius(), mTrackPaint); + } + postRepaint(); + } + + /** + * 设置背景渐变 + * 设置中心点之后再做此事 + * + * @param x pivot x + * @param y pivot y + * @param r radius of gradient + * @param sc start color + * @param ec end color + */ + public void setRadialGradient(float x, float y, float r, int sc, int ec) { + mBackgroundPaint = new Paint(); + mBackgroundPaint.setStyle(Paint.Style.FILL); + mBackgroundPaint.setAntiAlias(true); + mBackgroundPaint.setShader(new RadialGradient(x, y, r, sc, ec, Shader.TileMode.CLAMP)); + prepare(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + prepare(); + } + + @Override + protected void onDraw(Canvas canvas) { + if (planets.size() == 0) return; + + int count = canvas.save(); + if (mCacheBitmap != null) canvas.drawBitmap(mCacheBitmap, 0, 0, mPlanetPaint); + for (Planet planet : planets) { + double y; + double x; + float angle; + if (planet.isClockwise()) { + angle = (planet.getOriginAngle() + paintCount * planet.getAngleRate()) % 360; + } else { + angle = 360 - (planet.getOriginAngle() + paintCount * planet.getAngleRate()) % 360; + } + x = Math.cos(angle) * planet.getRadius() + pivotX; + y = Math.sin(angle) * planet.getRadius() + pivotY; + mPlanetPaint.setColor(planet.getColor()); + canvas.drawCircle((float) x, (float) y, planet.getSelfRadius(), mPlanetPaint); + } + canvas.restoreToCount(count); + ++paintCount; + if (paintCount < 0) paintCount = 0; + } + + private void postRepaint() { + removeCallbacks(this); + postDelayed(this, mFlushRate); + } + + @Override + public void run() { + invalidate(); + postRepaint(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + prepare(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mCacheBitmap != null) { + mCacheBitmap.recycle(); + mCacheBitmap = null; + } + } + + private ValueAnimator getAccelerateAnimator() { + if (mAccelerateAnimator != null) return mAccelerateAnimator; + mAccelerateAnimator = ValueAnimator.ofFloat(mFlushRate, FLUSH_RATE_LIMITATION); + mAccelerateAnimator.setEvaluator(new FloatEvaluator()); + mAccelerateAnimator.setInterpolator(new DecelerateInterpolator()); + mAccelerateAnimator.setDuration(1000); + mAccelerateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mFlushRate = ((Float) animation.getAnimatedValue()).intValue(); + } + }); + return mAccelerateAnimator; + } + + private ValueAnimator getDecelerateAnimator() { + if (mDecelerateAnimator != null) return mDecelerateAnimator; + mDecelerateAnimator = ValueAnimator.ofFloat(mFlushRate, FLUSH_RATE); + mDecelerateAnimator.setEvaluator(new FloatEvaluator()); + mDecelerateAnimator.setInterpolator(new AccelerateInterpolator()); + mDecelerateAnimator.setDuration(1000); + mDecelerateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mFlushRate = ((Float) animation.getAnimatedValue()).intValue(); + } + }); + return mDecelerateAnimator; + } + + // 都是在主线程内操作,所以不会有生产者消费者问题 + + public void accelerate() { + if (mFlushRate == FLUSH_RATE_LIMITATION) return; + mFlushRate = FLUSH_RATE; // reset flush rate + ValueAnimator animator = getAccelerateAnimator(); + if (animator.isRunning()) animator.cancel(); + animator.setFloatValues(mFlushRate, FLUSH_RATE_LIMITATION); + animator.start(); + } + + public void decelerate() { + if (mAccelerateAnimator != null && mAccelerateAnimator.isRunning()) { + mAccelerateAnimator.cancel(); + } + if (mFlushRate == FLUSH_RATE) return; + ValueAnimator animator = getDecelerateAnimator(); + if (animator.isRunning()) animator.cancel(); + long duration = (long) (((float) FLUSH_RATE - mFlushRate) / FLUSH_RATE * 1000); + if (duration == 0) { + mFlushRate = FLUSH_RATE; + return; + } + animator.setDuration(duration); + animator.setFloatValues(mFlushRate, FLUSH_RATE); + animator.start(); + } + + public static class Planet { + private int mRadius = 100; + private int mSelfRadius = 6; + private int mTrackWidth = 1; + private int mColor = 0XFF6FDB94; + private int mTrackColor = 0XFF6FDB94; + private float mAngleRate = 0.01F; + private int mOriginAngle = 0; + private boolean isClockwise = true; + + public int getRadius() { + return mRadius; + } + + public void setRadius(int mRadius) { + this.mRadius = mRadius; + } + + public int getSelfRadius() { + return mSelfRadius; + } + + public void setSelfRadius(int mSelfRadius) { + this.mSelfRadius = mSelfRadius; + } + + public int getTrackWidth() { + return mTrackWidth; + } + + public void setTrackWidth(int mTrackWidth) { + this.mTrackWidth = mTrackWidth; + } + + public int getColor() { + return mColor; + } + + public void setColor(int mColor) { + this.mColor = mColor; + } + + public int getTrackColor() { + return mTrackColor; + } + + public void setTrackColor(int mTrackColor) { + this.mTrackColor = mTrackColor; + } + + public float getAngleRate() { + return mAngleRate; + } + + public void setAngleRate(float mAngleRate) { + this.mAngleRate = mAngleRate; + } + + public boolean isClockwise() { + return isClockwise; + } + + public void setClockwise(boolean clockwise) { + isClockwise = clockwise; + } + + public int getOriginAngle() { + return mOriginAngle; + } + + public void setOriginAngle(int mOriginAngle) { + this.mOriginAngle = mOriginAngle; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/SystemConfigView.java b/app/src/main/java/net/oschina/app/improve/widget/SystemConfigView.java new file mode 100644 index 0000000000000000000000000000000000000000..dd59a41884107bd0aca679b528c7bfb5e07cb01c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/SystemConfigView.java @@ -0,0 +1,79 @@ +package net.oschina.app.improve.widget; + +import android.app.Application; +import android.content.Context; +import android.text.TextUtils; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; + +import net.oschina.app.BuildConfig; +import net.oschina.app.R; +import net.oschina.app.Setting; +import net.oschina.app.api.ApiHttpClient; +import net.oschina.app.util.TDevice; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ + +public class SystemConfigView extends LinearLayout { + + public static void show(ViewGroup root) { + if ((System.currentTimeMillis() - Setting.getSystemConfigTimeStamp(root.getContext())) < 4000) { + SystemConfigView v = new SystemConfigView(root.getContext()); + root.addView(v); + } + } + + public SystemConfigView(Context context) { + super(context); + inflate(context, R.layout.lay_system_config, this); + initUrlConfig((RadioGroup) findViewById(R.id.radio_group)); + } + + + private void initUrlConfig(RadioGroup group) { + String serverUrls = BuildConfig.API_SERVER_URL; + String curServerUrl = Setting.getServerUrl(getContext()); + serverUrls = TextUtils.isEmpty(serverUrls) ? curServerUrl : serverUrls; + final String[] urls = serverUrls.split(";"); + + for (int i = 0; i < urls.length; i++) { + String url = urls[i]; + if (TextUtils.isEmpty(url)) + continue; + RadioGroup.LayoutParams params = new RadioGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMargins(0, (int) TDevice.dipToPx(getResources(), 4), + 0, (int) TDevice.dipToPx(getResources(), 4)); + + + RadioButton button = new RadioButton(getContext()); + button.setLayoutParams(params); + button.setText(url); + button.setChecked(url.equals(curServerUrl)); + button.setId(i); + button.setTag(url); + button.setButtonDrawable(R.drawable.ic_selector_checkbox); + button.setPadding(0, 0, 0, 0); + group.addView(button); + } + + group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + Object obj = group.findViewById(checkedId).getTag(); + if (obj != null && obj instanceof String) { + Setting.updateServerUrl(getContext(), (String) obj); + ApiHttpClient.init((Application) getContext().getApplicationContext()); + } + } + }); + } + + +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/TabPickerView.java b/app/src/main/java/net/oschina/app/improve/widget/TabPickerView.java new file mode 100644 index 0000000000000000000000000000000000000000..ff4da53ff24f0a6f6ebf50f090afe795d07a2c27 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/TabPickerView.java @@ -0,0 +1,747 @@ +package net.oschina.app.improve.widget; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.Context; +import android.content.res.ColorStateList; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.widget.NestedScrollView; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import net.oschina.app.AppContext; +import net.oschina.app.BuildConfig; +import net.oschina.app.R; +import net.oschina.app.improve.bean.SubTab; +import net.oschina.app.util.TDevice; +import net.oschina.app.util.TLog; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * 动态栏目View 请通过{@link #setTabPickerManager(TabPickerDataManager)}来设置活动数据和原始数据,数据 + * 对象根据需要实现{@link Object#hashCode()}和{@link Object#equals(Object)}方法,因为非活动数据是通过使用 + * {@link List#contains(Object)}方法从原始数据剔除活动数据实现的。 + *

    + *

    活动动态栏目的添加、删除、移动、选择通过{@link OnTabPickingListener}来实现的,你可以通过方法 + * {@link #setOnTabPickingListener(OnTabPickingListener)}来监听。 + *

    + *

    通过{@link #show(int)}和{@link #hide()}方法来显示隐藏动态栏目界面。 + *

    + *

    通过{@link #onTurnBack()}响应回退事件。 + *

    + *

    Created by thanatosx on 16/10/27. + */ +@SuppressWarnings("all") +public class TabPickerView extends FrameLayout { + + private TextView mViewDone; + private TextView mViewOperator; + private RecyclerView mRecyclerActive; + private RecyclerView mRecyclerInactive; + private LinearLayout mLayoutWrapper; + private RelativeLayout mLayoutTop; + private LinearLayout mViewWrapper; + private NestedScrollView mViewScroller; + private ItemTouchHelper mItemTouchHelper; + + private TabAdapter mActiveAdapter; + private TabAdapter mInactiveAdapter; + + private TabPickerDataManager mTabManager; + private OnTabPickingListener mTabPickingListener; + + private Action1 mOnShowAnimator; + private Action1 mOnHideAnimator; + + private int mSelectedIndex = 0; + + public TabPickerView(Context context) { + this(context, null); + } + + public TabPickerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TabPickerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initWidgets(); + } + + /** + * The Tab Picking Listener Interface + */ + public interface OnTabPickingListener { + /** + * 单击选择某个tab + * + * @param position select a tab + */ + void onSelected(int position); + + /** + * 删除某个tab + * + * @param position the moved tab's position + * @param tab the moved tab + */ + void onRemove(int position, SubTab tab); + + /** + * 添加某个tab + * + * @param tab the inserted tab + */ + void onInsert(SubTab tab); + + /** + * 交换tab + * + * @param op the mover's position + * @param mover the moving tab + * @param np the swapper's position + * @param swapper the swapped tab + */ + void onMove(int op, int np); + + /** + * 重新持久化活动的tabs + * + * @param activeTabs the actived tabs + */ + void onRestore(List activeTabs); + } + + public interface Action1 { + void call(T t); + } + + private void initWidgets() { + View view = LayoutInflater.from(getContext()) + .inflate(R.layout.view_tab_picker, this, false); + + mRecyclerActive = (RecyclerView) view.findViewById(R.id.view_recycler_active); + mRecyclerInactive = (RecyclerView) view.findViewById(R.id.view_recycler_inactive); + mViewScroller = (NestedScrollView) view.findViewById(R.id.view_scroller); + mLayoutTop = (RelativeLayout) view.findViewById(R.id.layout_top); + mViewWrapper = (LinearLayout) view.findViewById(R.id.view_wrapper); + mViewDone = (TextView) view.findViewById(R.id.tv_done); + mViewOperator = (TextView) view.findViewById(R.id.tv_operator); + mLayoutWrapper = (LinearLayout) view.findViewById(R.id.layout_wrapper); + mViewDone.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mViewDone.getText().toString().equals("排序删除")) { + mActiveAdapter.startEditMode(); + } else { + mActiveAdapter.cancelEditMode(); + } + } + }); + + addView(view); + } + + public void setOnShowAnimation(Action1 l) { + this.mOnShowAnimator = l; + } + + public void setOnHideAnimator(Action1 l) { + this.mOnHideAnimator = l; + } + + public void show(int selectedIndex) { + final int tempIndex = mSelectedIndex; + mSelectedIndex = selectedIndex; + mActiveAdapter.notifyItemChanged(tempIndex); + mActiveAdapter.notifyItemChanged(mSelectedIndex); + + if (mOnShowAnimator != null) mOnShowAnimator.call(null); + setVisibility(VISIBLE); + mLayoutTop.setAlpha(0); + mLayoutTop.animate().alpha(1).setDuration(380).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mLayoutTop.setAlpha(1); + } + }); + + mViewScroller.setTranslationY(-mViewScroller.getHeight()); + mViewScroller.animate().translationY(0).setDuration(380).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mViewScroller.setTranslationY(0); + } + }); + } + + public void hide() { + if (mTabPickingListener != null) { + mTabPickingListener.onRestore(mTabManager.mActiveDataSet); + mTabPickingListener.onSelected(mSelectedIndex); + } + + if (mOnHideAnimator != null) mOnHideAnimator.call(null); + mLayoutTop.animate().alpha(0).setDuration(380).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + setVisibility(GONE); + } + }); + mViewScroller.animate().translationY(-mViewScroller.getHeight()).setDuration(380); + } + + private void initRecyclerView() { + if (mRecyclerActive.getAdapter() != null && mRecyclerInactive.getAdapter() != null) return; + + /* - set up Active Recycler - */ + mActiveAdapter = new TabAdapter(mTabManager.mActiveDataSet) { + @Override + public TabAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new TabAdapter.ViewHolder(LayoutInflater.from( + parent.getContext()).inflate(R.layout.view_tab_item, parent, false)); + } + + @Override + public void onBindViewHolder(TabAdapter.ViewHolder holder, int position) { + SubTab item = items.get(position); + holder.mViewTab.setText(item.getName()); + if (item.isFixed()) { + holder.mViewTab.setActivated(false); + } else { + holder.mViewTab.setActivated(true); + } + if (mSelectedIndex == position) { + holder.mViewTab.setSelected(true); + } else { + holder.mViewTab.setSelected(false); + } + if (!TextUtils.isEmpty(item.getTag())) { + holder.mViewBubble.setText(item.getTag()); + holder.mViewBubble.setVisibility(VISIBLE); + } else { + holder.mViewBubble.setVisibility(GONE); + } + if (isEditMode() && !item.isFixed()) { + holder.mViewDel.setVisibility(VISIBLE); + } else { + holder.mViewDel.setVisibility(GONE); + } + if (mBindViewObserver != null) { + mBindViewObserver.call(holder); + } + } + + @Override + public int getItemCount() { + return items.size(); + } + + }; + + mActiveAdapter.setOnClickItemListener(new Action1() { + @Override + public void call(Integer position) { + // 先调用隐藏, 因为隐藏有保存,更新tab的动作 + int tempIndex = mSelectedIndex; + mSelectedIndex = position; + mActiveAdapter.notifyItemChanged(tempIndex); + mActiveAdapter.notifyItemChanged(mSelectedIndex); + hide(); + } + }); + + mActiveAdapter.setOnDeleteItemListener(new Action1() { + @Override + public void call(Integer position) { + SubTab tab = mActiveAdapter.getItem(position); + if (tab == null || tab.isFixed()) return; + int oldCount = mActiveAdapter.getItemCount(); + tab = mActiveAdapter.removeItem(position); + // 放到下面需要根据Original DataSet重排序 + for (SubTab item : mTabManager.mOriginalDataSet) { + if (!item.getToken().equals(tab.getToken())) continue; + tab.setOrder(item.getOrder()); + break; + } + + int i = 0; + for (; i < mTabManager.mInactiveDataSet.size(); i++) { + SubTab item = mTabManager.mInactiveDataSet.get(i); + if (item.getOrder() < tab.getOrder()) continue; + break; + } + mTabManager.mInactiveDataSet.add(i, tab); + mInactiveAdapter.notifyItemInserted(i); + + if (mSelectedIndex == position) { + mSelectedIndex = position == oldCount - 1 ? mSelectedIndex - 1 : mSelectedIndex; + mActiveAdapter.notifyItemChanged(mSelectedIndex); + } else if (mSelectedIndex > position) { + --mSelectedIndex; + mActiveAdapter.notifyItemChanged(mSelectedIndex); + } + if (mTabPickingListener != null) { + mTabPickingListener.onRemove(position, tab); +// mTabPickingListener.onSelected(mSelectedIndex); + } + } + }); + mRecyclerActive.setAdapter(mActiveAdapter); + + mItemTouchHelper = new ItemTouchHelper(mActiveAdapter.newItemTouchHelperCallback()); + mItemTouchHelper.attachToRecyclerView(mRecyclerActive); + mRecyclerActive.setLayoutManager(new GridLayoutManager(getContext(), 4)); + + /* - set up Inactive Recycler - */ + mRecyclerInactive.setLayoutManager(new GridLayoutManager(getContext(), 4)); + mInactiveAdapter = new TabAdapter(mTabManager.mInactiveDataSet) { + @Override + public TabAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new TabAdapter.ViewHolder(LayoutInflater.from( + parent.getContext()).inflate(R.layout.view_tab_item, parent, false)); + } + + @Override + public void onBindViewHolder(TabAdapter.ViewHolder holder, int position) { + holder.mViewTab.setText(items.get(position).getName()); + } + + @Override + public int getItemCount() { + return items.size(); + } + }; + mInactiveAdapter.setOnClickItemListener(new Action1() { + @Override + public void call(Integer position) { + // fix double click会out of array + if (position < 0 || position >= mInactiveAdapter.getItemCount()) return; + SubTab tab = mInactiveAdapter.removeItem(position); + mActiveAdapter.addItem(tab); + if (mTabPickingListener != null) { + mTabPickingListener.onInsert(tab); + } + } + }); + mRecyclerInactive.setAdapter(mInactiveAdapter); + } + + + public void setTabPickerManager(TabPickerDataManager manager) { + if (manager == null) return; + mTabManager = manager; + initRecyclerView(); + } + + public TabPickerDataManager getTabPickerManager() { + return mTabManager; + } + + public void setOnTabPickingListener(OnTabPickingListener l) { + mTabPickingListener = l; + } + + public boolean onTurnBack() { + if (mActiveAdapter.isEditMode()) { + mActiveAdapter.cancelEditMode(); + return true; + } + if (getVisibility() == VISIBLE) { + hide(); + return true; + } + return false; + } + + /** + * Class TabAdapter + * + * @param + */ + private abstract class TabAdapter + extends RecyclerView.Adapter { + + private View.OnClickListener mClickDeleteListener; + private View.OnClickListener mClickTabItemListener; + private View.OnTouchListener mTouchTabItemListener; + + private Action1 mDeleteItemAction; + private Action1 mSelectItemAction; + Action1 mBindViewObserver; + + private boolean isEditMode = false; + List items; + + TabAdapter(List items) { + this.items = items; + } + + SubTab removeItem(int position) { + SubTab b = items.remove(position); + notifyItemRemoved(position); + return b; + } + + void addItem(SubTab bean) { + items.add(bean); + notifyItemInserted(items.size() - 1); + } + + void addItem(SubTab bean, int index) { + items.add(index, bean); + notifyItemInserted(index); + } + + SubTab getItem(int position) { + if (position < 0 || position >= items.size()) return null; + return items.get(position); + } + + void startEditMode() { + mViewOperator.setText("拖动排序"); + mViewDone.setText("完成"); + + mLayoutWrapper.setVisibility(GONE); + int sh = mViewScroller.getMeasuredHeight(); + int rh = mRecyclerActive.getHeight(); + Log.i("oschina", "sh: " + sh + " rh: " + rh); + if (rh < sh) { + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mRecyclerActive.getLayoutParams(); + params.height = sh; + mRecyclerActive.setLayoutParams(params); + } + + isEditMode = true; + notifyDataSetChanged(); + } + + void cancelEditMode() { + mViewOperator.setText("切换栏目"); + mViewDone.setText("排序删除"); + + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mRecyclerActive.getLayoutParams(); + params.height = params.WRAP_CONTENT; + mRecyclerActive.setLayoutParams(params); + mLayoutWrapper.setVisibility(VISIBLE); + + + isEditMode = false; + notifyDataSetChanged(); + } + + boolean isEditMode() { + return isEditMode; + } + + void registerBindViewObserver(Action1 l) { + this.mBindViewObserver = l; + } + + void unRegisterBindViewObserver() { + this.mBindViewObserver = null; + } + + OnClickListener getClickTabItemListener() { + if (mClickTabItemListener == null) { + mClickTabItemListener = new OnClickListener() { + @Override + public void onClick(View v) { + ViewHolder holder = (ViewHolder) v.getTag(); + if (holder == null) return; + if (mSelectItemAction != null) { + mSelectItemAction.call(holder.getAdapterPosition()); + } + } + }; + } + return mClickTabItemListener; + } + + OnClickListener getDeleteItemListener() { + if (mClickDeleteListener == null) { + mClickDeleteListener = new OnClickListener() { + @Override + public void onClick(View v) { + ViewHolder holder = (ViewHolder) v.getTag(); + if (holder == null) return; + if (mDeleteItemAction != null) { + mDeleteItemAction.call(holder.getAdapterPosition()); + } + } + }; + } + return mClickDeleteListener; + } + + OnTouchListener getTouchTabItemListener() { + if (mTouchTabItemListener == null) { + mTouchTabItemListener = new OnTabTouchListener(); + } + return mTouchTabItemListener; + } + + void setOnClickItemListener(Action1 l) { + this.mSelectItemAction = l; + } + + void setOnDeleteItemListener(Action1 l) { + this.mDeleteItemAction = l; + } + + TabItemTouchHelperCallback newItemTouchHelperCallback() { + return new TabItemTouchHelperCallback(); + } + + /** + * Tab View Holder + */ + class ViewHolder extends RecyclerView.ViewHolder { + + TextView mViewTab; + TextView mViewBubble; + ImageView mViewDel; + + ViewHolder(View view) { + super(view); + mViewTab = (TextView) view.findViewById(R.id.tv_content); + mViewBubble = (TextView) view.findViewById(R.id.tv_bubble); + mViewDel = (ImageView) view.findViewById(R.id.iv_delete); + + mViewTab.setTextColor(new ColorStateList(new int[][]{ + new int[]{-android.R.attr.state_activated}, + new int[]{} + }, new int[]{0XFF24CF5F, 0XFF6A6A6A}) + ); + mViewTab.setActivated(true); + + mViewTab.setTag(this); + mViewDel.setTag(this); + mViewDel.setOnClickListener(getDeleteItemListener()); + mViewTab.setOnClickListener(getClickTabItemListener()); + mViewTab.setOnTouchListener(getTouchTabItemListener()); + } + } + + /** + * Inner Tab Touch Listener + */ + private class OnTabTouchListener implements OnTouchListener { + + @Override + public boolean onTouch(View v, MotionEvent event) { + ViewHolder holder = (ViewHolder) v.getTag(); + if (holder == null) return false; + if (isEditMode() && MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { + mItemTouchHelper.startDrag(holder); + return true; + } + return false; + } + } + + class TabItemTouchHelperCallback extends ItemTouchHelper.Callback { + + @Override + public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + int dragFlag = 0; + int position = viewHolder.getAdapterPosition(); + if (position > 0 && position < items.size()) { + if (!items.get(position).isFixed()) { + dragFlag = ItemTouchHelper.UP + | ItemTouchHelper.DOWN + | ItemTouchHelper.LEFT + | ItemTouchHelper.RIGHT; + } + } + return makeMovementFlags(dragFlag, 0); + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, + RecyclerView.ViewHolder target) { + int fromTargetIndex = viewHolder.getAdapterPosition(); + int toTargetIndex = target.getAdapterPosition(); + + if (fromTargetIndex == toTargetIndex) return true; + if (items.get(toTargetIndex).isFixed()) return true; + + SubTab tab = items.remove(fromTargetIndex); + items.add(toTargetIndex, tab); + + if (mSelectedIndex == fromTargetIndex) { + mSelectedIndex = toTargetIndex; + } else if (mSelectedIndex == toTargetIndex) { + mSelectedIndex = fromTargetIndex > toTargetIndex ? mSelectedIndex + 1 : mSelectedIndex - 1; + } else if (toTargetIndex <= mSelectedIndex && mSelectedIndex < fromTargetIndex) { + ++mSelectedIndex; + } else if (fromTargetIndex < mSelectedIndex && mSelectedIndex < toTargetIndex) { + --mSelectedIndex; + } + + notifyItemMoved(fromTargetIndex, toTargetIndex); + if (mTabPickingListener != null) { + mTabPickingListener.onMove(fromTargetIndex, toTargetIndex); + } + return true; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + // pass + } + + @Override + public void onSelectedChanged(final RecyclerView.ViewHolder viewHolder, int actionState) { + super.onSelectedChanged(viewHolder, actionState); + if (viewHolder == null) return; + ((ViewHolder) viewHolder).mViewTab.setSelected(true); + if (isEditMode()) return; + final int position = viewHolder.getAdapterPosition(); + + // onBindViewHolder之后,ViewHolder.itemView.getParent() != RecycleView + // 估计是在onBindViewHolder之后绑定了ViewParent的,延迟500,暂时没什么好办法 + registerBindViewObserver(new Action1() { + @Override + public void call(final ViewHolder viewHolder) { + int index = viewHolder.getAdapterPosition(); + if (index != position) return; + postDelayed(new Runnable() { + @Override + public void run() { + mItemTouchHelper.startDrag(viewHolder); + } + }, 500); + unRegisterBindViewObserver(); + } + }); + startEditMode(); + } + + @Override + public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + if (mSelectedIndex == viewHolder.getAdapterPosition()) return; + ((ViewHolder) viewHolder).mViewTab.setSelected(false); + } + } + + } + + /** + * Tab Data Picker Manager, Manager the Tab Data's behavior + * + * @param + */ + public abstract static class TabPickerDataManager { + + public List mActiveDataSet; + public List mInactiveDataSet; + public List mOriginalDataSet; + + public TabPickerDataManager() { + mActiveDataSet = setupActiveDataSet(); + mOriginalDataSet = setupOriginalDataSet(); + mInactiveDataSet = new ArrayList<>(); + + if (mOriginalDataSet == null || mOriginalDataSet.size() == 0) { + throw new RuntimeException("Original Data Set can't be null or empty"); + } + + TLog.i("oschina", "Active Data Set: " + (mActiveDataSet == null ? "true" : "" + mActiveDataSet.size())); + if (mActiveDataSet == null) { + mActiveDataSet = new ArrayList<>(); + for (SubTab item : mOriginalDataSet) { + if (item.isActived() || item.isFixed()) { + mActiveDataSet.add(item); + } + } + restoreActiveDataSet(mActiveDataSet); + } else if (isUpdate()) { + List mActiveList = new ArrayList<>(); + + // 替换老列表项 + for (SubTab item : mActiveDataSet) { + int position = mOriginalDataSet.indexOf(item); + if (position == -1) continue; + mActiveList.add(mOriginalDataSet.get(position)); + } + + // 将未加入的新项加入活动列表 + for (SubTab item : mOriginalDataSet) { + if (item.isActived() && !mActiveList.contains(item)) { + mActiveList.add(item); + } + } + + mActiveDataSet = mActiveList; + mActiveList = null; + restoreActiveDataSet(mActiveDataSet); + } + + Collections.sort(mActiveDataSet, new Comparator() { + @Override + public int compare(SubTab o1, SubTab o2) { + if (o1.isFixed() && !o2.isFixed()) return -1; + if (!o1.isFixed() && o2.isFixed()) return 1; + return 0; +// return o1.getOrder() - o2.getOrder(); + } + }); + + for (SubTab item : mOriginalDataSet) { + if (mActiveDataSet.contains(item)) continue; + mInactiveDataSet.add(item); + } + } + + + public static boolean isUpdate() { + int mVersionCode = TDevice.getVersionCode(); + int mask = AppContext.get("TabsMask", -1); + TLog.i("oschina", "Current Version Tree: " + mVersionCode + ", Mask Version Tree: " + mask); + if (BuildConfig.DEBUG) return true; + return mVersionCode != mask; + } + + public List getActiveDataSet() { + return mActiveDataSet; + } + + public List getInActiveDataSet() { + return mInactiveDataSet; + } + + public List getOriginalDataSet() { + return mOriginalDataSet; + } + + public abstract List setupActiveDataSet(); + + public abstract List setupOriginalDataSet(); + + public abstract void restoreActiveDataSet(List mActiveDataSet); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/TitleBar.java b/app/src/main/java/net/oschina/app/improve/widget/TitleBar.java new file mode 100644 index 0000000000000000000000000000000000000000..95ec0e8a33fc1e1e0d47f8b37e4b94124d086901 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/TitleBar.java @@ -0,0 +1,130 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.annotation.DrawableRes; +import android.support.annotation.RequiresApi; +import android.support.annotation.StringRes; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.utils.UiUtil; +import net.oschina.app.util.TDevice; + +/** + * Created by JuQiu + * on 16/9/5. + */ +public class TitleBar extends FrameLayout { + private static int EXT_PADDING_TOP; + private TextView mTitle; + private ImageView mIcon; + + + public TitleBar(Context context) { + super(context); + init(null, 0, 0); + } + + public TitleBar(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs, 0, 0); + } + + public TitleBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr, 0); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public TitleBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(attrs, defStyleAttr, defStyleRes); + } + + + private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) { + Context context = getContext(); + + LayoutInflater inflater = LayoutInflater.from(context); + inflater.inflate(R.layout.lay_title_bar, this, true); + + mTitle = (TextView) findViewById(R.id.tv_title); + mIcon = (ImageView) findViewById(R.id.iv_icon); + + + if (attrs != null) { + // Load attributes + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.TitleBar, defStyleAttr, defStyleRes); + + String title = a.getString(R.styleable.TitleBar_aTitle); + Drawable drawable = a.getDrawable(R.styleable.TitleBar_aIcon); + a.recycle(); + + mTitle.setText(title); + mIcon.setImageDrawable(drawable); + } else { + mIcon.setVisibility(GONE); + } + + // Set Background + setBackgroundColor(getResources().getColor(R.color.main_green)); + + // Init padding + setPadding(getLeft(), getTop() + UiUtil.getStatusBarHeight(getContext()), getRight(), getBottom()); + } + + public void setTitle(@StringRes int titleRes) { + if (titleRes <= 0) + return; + mTitle.setText(titleRes); + } + + public void setIcon(@DrawableRes int iconRes) { + if (iconRes <= 0) { + mIcon.setVisibility(GONE); + return; + } + mIcon.setImageResource(iconRes); + mIcon.setVisibility(VISIBLE); + } + + public void setIconOnClickListener(OnClickListener listener) { + mIcon.setOnClickListener(listener); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + float d = getResources().getDisplayMetrics().density; + int minH = (int) (d * 36 + UiUtil.getStatusBarHeight(getContext())); + + heightMeasureSpec = MeasureSpec.makeMeasureSpec(minH, MeasureSpec.EXACTLY); + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + public static int getExtPaddingTop(Resources resources) { + if (EXT_PADDING_TOP > 0) + return EXT_PADDING_TOP; + + try { + Class clazz = Class.forName("com.android.internal.R$dimen"); + Object object = clazz.newInstance(); + int height = Integer.parseInt(clazz.getField("status_bar_height") + .get(object).toString()); + EXT_PADDING_TOP = resources.getDimensionPixelSize(height); + } catch (Exception e) { + e.printStackTrace(); + return (int) TDevice.dp2px(25); + } + return EXT_PADDING_TOP; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/TweetPicturesLayout.java b/app/src/main/java/net/oschina/app/improve/widget/TweetPicturesLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..f6710b30602d07c93f0431ee0ab5a21d7f01ec07 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/TweetPicturesLayout.java @@ -0,0 +1,329 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.bumptech.glide.BitmapRequestBuilder; +import com.bumptech.glide.DrawableRequestBuilder; +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.improve.bean.Tweet; +import net.oschina.app.improve.media.ImageGalleryActivity; +import net.oschina.common.utils.CollectionUtil; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by JuQiu + * on 16/8/26. + */ +public class TweetPicturesLayout extends ViewGroup implements View.OnClickListener { + private static final int SINGLE_MAX_W = 120; + private static final int SINGLE_MAX_H = 180; + private static final int SINGLE_MIN_W = 34; + private static final int SINGLE_MIN_H = 34; + + private Tweet.Image[] mImages; + private float mVerticalSpacing; + private float mHorizontalSpacing; + private int mColumn; + private int mMaxPictureSize; + + public TweetPicturesLayout(Context context) { + this(context, null); + } + + public TweetPicturesLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TweetPicturesLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs, defStyleAttr, 0); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public TweetPicturesLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(attrs, defStyleAttr, defStyleRes); + } + + private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) { + final Context context = getContext(); + final Resources resources = getResources(); + final float density = resources.getDisplayMetrics().density; + + int vSpace = (int) (4 * density); + int hSpace = vSpace; + + if (attrs != null) { + // Load attributes + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.TweetPicturesLayout, defStyleAttr, defStyleRes); + + // Load clip touch corner radius + vSpace = a.getDimensionPixelOffset(R.styleable.TweetPicturesLayout_verticalSpace, vSpace); + hSpace = a.getDimensionPixelOffset(R.styleable.TweetPicturesLayout_horizontalSpace, hSpace); + setColumn(a.getInt(R.styleable.TweetPicturesLayout_column, 3)); + setMaxPictureSize(a.getDimensionPixelOffset(R.styleable.TweetPicturesLayout_maxPictureSize, 0)); + a.recycle(); + } + + setVerticalSpacing(vSpace); + setHorizontalSpacing(hSpace); + } + + public void setHorizontalSpacing(float pixelSize) { + mHorizontalSpacing = pixelSize; + } + + public void setVerticalSpacing(float pixelSize) { + mVerticalSpacing = pixelSize; + } + + public void setColumn(int column) { + if (column < 1) + column = 1; + if (column > 20) + column = 20; + mColumn = column; + } + + public void setMaxPictureSize(int maxPictureSize) { + if (maxPictureSize < 0) + maxPictureSize = 0; + mMaxPictureSize = maxPictureSize; + } + + public void setImage(Tweet.Image[] images) { + if (mImages == images) + return; + + // 移除布局 + removeAllImage(); + + // 过滤掉不合法的数据 + if (images != null) { + List isOkImages = new ArrayList<>(); + for (Tweet.Image image : images) { + if (Tweet.Image.check(image)) + isOkImages.add(image); + } + images = CollectionUtil.toArray(isOkImages, Tweet.Image.class); + } + + // 赋值 + mImages = images; + + if (mImages != null && mImages.length > 0) { + LayoutInflater inflater = LayoutInflater.from(this.getContext()); + RequestManager requestManager = Glide.with(getContext()); + for (int i = 0; i < mImages.length; i++) { + Tweet.Image image = mImages[i]; + if (!Tweet.Image.check(image)) + continue; + + View view = inflater.inflate(R.layout.lay_tweet_image_item, this, false); + view.setTag(i); + view.setOnClickListener(this); + String path = image.getThumb(); + DrawableRequestBuilder builder = requestManager.load(path) + //.asBitmap() + .centerCrop() + //.placeholder(R.color.grey_50) + .error(R.mipmap.ic_split_graph); + + if (path.toLowerCase().endsWith("gif")) { + //builder = builder.diskCacheStrategy(DiskCacheStrategy.SOURCE); + view.findViewById(R.id.iv_is_gif).setVisibility(VISIBLE); + } + addView(view); + builder.into((ImageView) view.findViewById(R.id.iv_picture)); + } + + // all do requestLayout + if (getVisibility() == VISIBLE) { + requestLayout(); + } else { + setVisibility(View.VISIBLE); + } + } else { + setVisibility(View.GONE); + } + } + + public void setImage(String[] images) { + if (images == null || images.length == 0) return; + Tweet.Image[] ims = new Tweet.Image[images.length]; + for (int i = 0; i < images.length; i++) { + ims[i] = Tweet.Image.create(images[i]); + } + setImage(ims); + } + + public void removeAllImage() { + removeAllViews(); + mImages = null; + } + + private int getMaxChildSize(int size) { + if (mMaxPictureSize == 0) + return size; + else + return Math.min(mMaxPictureSize, size); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int paddingLeft = getPaddingLeft(); + int paddingTop = getPaddingTop(); + int paddingRight = getPaddingRight(); + int paddingBottom = getPaddingBottom(); + + int selfWidth = resolveSize(paddingLeft + paddingRight, widthMeasureSpec); + int wantedHeight = paddingBottom + paddingTop; + final int childCount = getChildCount(); + + + //noinspection StatementWithEmptyBody + if (childCount == 0) { + // Not have child we can only need padding size + } else if (childCount == 1) { + Tweet.Image image = mImages[0]; + if (Tweet.Image.check(image)) { + int imageW = image.getW(); + int imageH = image.getH(); + imageW = imageW <= 0 ? 100 : imageW; + imageH = imageH <= 0 ? 100 : imageH; + + float density = getResources().getDisplayMetrics().density; + // Get max width and height + float maxContentW = Math.min(selfWidth - paddingRight - paddingLeft, density * SINGLE_MAX_W); + float maxContentH = density * SINGLE_MAX_H; + + int childW, childH; + + float hToW = imageH / (float) imageW; + if (hToW > (maxContentH / maxContentW)) { + childH = (int) maxContentH; + childW = (int) (maxContentH / hToW); + } else { + childW = (int) maxContentW; + childH = (int) (maxContentW * hToW); + } + // Check the width and height below Min values + int minW = (int) (SINGLE_MIN_W * density); + if (childW < minW) + childW = minW; + int minH = (int) (SINGLE_MIN_H * density); + if (childH < minH) + childH = minH; + + View child = getChildAt(0); + if (child != null) { + child.measure(MeasureSpec.makeMeasureSpec(childW, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(childH, MeasureSpec.EXACTLY)); + wantedHeight += childH; + } + } + } else { + // Measure all child + final float maxContentWidth = selfWidth - paddingRight - paddingLeft - mHorizontalSpacing * (mColumn - 1); + // Get child size + final int childSize = getMaxChildSize((int) (maxContentWidth / mColumn)); + + for (int i = 0; i < childCount; ++i) { + View childView = getChildAt(i); + childView.measure(MeasureSpec.makeMeasureSpec(childSize, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(childSize, MeasureSpec.EXACTLY)); + } + + int lines = (int) (childCount / (float) mColumn + 0.9); + wantedHeight += (int) (lines * childSize + mVerticalSpacing * (lines - 1)); + } + + setMeasuredDimension(selfWidth, resolveSize(wantedHeight, heightMeasureSpec)); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + float childCount = getChildCount(); + if (childCount > 0) { + int paddingLeft = getPaddingLeft(); + int paddingTop = getPaddingTop(); + + if (childCount == 1) { + View childView = getChildAt(0); + int childWidth = childView.getMeasuredWidth(); + int childHeight = childView.getMeasuredHeight(); + childView.layout(paddingLeft, paddingTop, paddingLeft + childWidth, paddingTop + childHeight); + } else { + int mWidth = r - l; + int paddingRight = getPaddingRight(); + + int lineHeight = 0; + int childLeft = paddingLeft; + int childTop = paddingTop; + + for (int i = 0; i < childCount; ++i) { + View childView = getChildAt(i); + + if (childView.getVisibility() == View.GONE) { + continue; + } + + int childWidth = childView.getMeasuredWidth(); + int childHeight = childView.getMeasuredHeight(); + + lineHeight = Math.max(childHeight, lineHeight); + + if (childLeft + childWidth + paddingRight > mWidth) { + childLeft = paddingLeft; + childTop += mVerticalSpacing + lineHeight; + lineHeight = childHeight; + } + childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); + childLeft += childWidth + mHorizontalSpacing; + } + } + } + } + + @Override + public void onClick(View v) { + Tweet.Image[] images = mImages; + if (images == null || images.length <= 0) + return; + + Object obj = v.getTag(); + if (obj == null || !(obj instanceof Integer)) + return; + + int index = (int) obj; + if (index < 0) + index = 0; + if (index >= images.length) + index = images.length - 1; + + Tweet.Image image = images[index]; + if (!Tweet.Image.check(image)) + return; + + String[] paths = Tweet.Image.getImagePath(images); + if (paths == null || paths.length <= 0) + return; + + ImageGalleryActivity.show(getContext(), paths, index); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/ViewEventBanner.java b/app/src/main/java/net/oschina/app/improve/widget/ViewEventBanner.java new file mode 100644 index 0000000000000000000000000000000000000000..dc46677b59b9811bc52f84af6310b095f330cc49 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/ViewEventBanner.java @@ -0,0 +1,57 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.RelativeLayout; + +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.bean.Banner; +import net.oschina.app.improve.bean.News; +import net.oschina.app.improve.detail.general.EventDetailActivity; +import net.oschina.app.util.UIHelper; + +/** + * Created by huanghaibin + * on 16-5-23. + */ +public class ViewEventBanner extends RelativeLayout implements View.OnClickListener { + private Banner banner; + private ImageView mImageEnent; + + public ViewEventBanner(Context context) { + super(context, null); + init(context); + } + + private void init(Context context) { + LayoutInflater.from(context).inflate(R.layout.view_event_banner, this, true); + mImageEnent = (ImageView) findViewById(R.id.iv_event); + setOnClickListener(this); + } + + public void initData(RequestManager manager, Banner banner) { + this.banner = banner; + manager.load(banner.getImg()) + .placeholder(R.mipmap.event_cover_default) + .error(R.mipmap.event_cover_default) + .into(mImageEnent); + } + + @Override + public void onClick(View v) { + if (banner != null) { + int type = banner.getType(); + long id = banner.getId(); + if (type == News.TYPE_HREF) { + UIHelper.openExternalBrowser(getContext(), banner.getHref()); + } else { + EventDetailActivity.show(getContext(), id); + } + } + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/ViewNewsBanner.java b/app/src/main/java/net/oschina/app/improve/widget/ViewNewsBanner.java new file mode 100644 index 0000000000000000000000000000000000000000..56054206a5dcf1d6ac550d262d5a70cb9dcb9680 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/ViewNewsBanner.java @@ -0,0 +1,52 @@ +package net.oschina.app.improve.widget; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.RelativeLayout; + +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.bean.Banner; +import net.oschina.app.util.UIHelper; + +/** + * Created by huanghaibin + * on 16-5-23. + */ +public class ViewNewsBanner extends RelativeLayout implements View.OnClickListener { + private Banner banner; + private ImageView iv_banner; + + public ViewNewsBanner(Context context) { + super(context, null); + init(context); + } + + private void init(Context context) { + LayoutInflater.from(context).inflate(R.layout.view_news_banner, this, true); + iv_banner = (ImageView) findViewById(R.id.iv_banner); + //setOnClickListener(this); + } + + public void initData(RequestManager manager, Banner banner) { + this.banner = banner; + manager.load(banner.getImg()).into(iv_banner); + } + + @Override + public void onClick(View v) { + if (banner != null) { + int type = banner.getType(); + long id = banner.getId(); + UIHelper.showDetail(getContext(), type, id, banner.getHref()); + } + } + + + public String getTitle() { + return banner.getName(); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/adapter/OnKeyArrivedListenerAdapter.java b/app/src/main/java/net/oschina/app/improve/widget/adapter/OnKeyArrivedListenerAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..4fcbd329b4ead741adf3207780a8616c21b3b227 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/adapter/OnKeyArrivedListenerAdapter.java @@ -0,0 +1,100 @@ +package net.oschina.app.improve.widget.adapter; + +import android.app.Activity; +import android.content.Context; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.Spanned; +import android.text.TextUtils; + +import net.oschina.app.improve.tweet.activities.TweetTopicActivity; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; +import net.oschina.common.widget.RichEditText; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ + +public class OnKeyArrivedListenerAdapter implements RichEditText.OnKeyArrivedListener { + private Object mHost; + + public OnKeyArrivedListenerAdapter(Object host) { + if (host instanceof Activity || host instanceof Fragment || host instanceof android.app.Fragment) { + mHost = host; + } else { + throw new RuntimeException("Host 必须是 Activity/Fragment"); + } + } + + @Override + public boolean onMentionKeyArrived(RichEditText editText) { + Editable msg = editText.getText(); + String msgStr = msg.toString(); + if (TextUtils.isEmpty(msgStr)) + return false; + int selStartIndex = editText.getSelectionStart(); + + if (TextUtils.isEmpty(msgStr.trim()) || selStartIndex <= 0 + || TextUtils.isEmpty(msgStr.substring(selStartIndex - 1, selStartIndex).trim())) { + skipMention(editText); + } + + return true; + } + + @Override + public boolean onTopicKeyArrived(RichEditText editText) { + Editable msg = editText.getText(); + String msgStr = msg.toString(); + int selStartIndex = editText.getSelectionStart(); + int selEndIndex = editText.getSelectionEnd(); + + if (TextUtils.isEmpty(msgStr.trim()) || selStartIndex <= 0) { + skipTopic(editText); + return true; + } + + int startIndex = 0; + RichEditText.TagSpan[] spans = msg.getSpans(0, selStartIndex, RichEditText.TagSpan.class); + if (spans.length > 0) { + startIndex = msg.getSpanEnd(spans[spans.length - 1]); + } + + boolean isMatcher = false; + String tagStr = msgStr.substring(startIndex, selStartIndex) + "#"; + Pattern pattern = Pattern.compile(RichEditText.MATCH_TOPIC); + Matcher matcher = pattern.matcher(tagStr); + while (matcher.find()) { + String str = matcher.group(); + int matcherStart = matcher.start() + startIndex; + int matcherEnd = matcher.end() + startIndex; + if (matcherEnd == selStartIndex + 1) + msg.replace(selStartIndex, selEndIndex, "#"); + msg.setSpan(new RichEditText.TagSpan(str), matcherStart, matcherEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + isMatcher = true; + } + if (isMatcher) { + editText.setSelection(selEndIndex); + return false; + } + + skipTopic(editText); + return true; + } + + private void skipMention(RichEditText editText) { +// Context context = editText.getContext(); +// if (context != null && mHost != null) +// //UserSelectFriendsActivity.show(mHost, editText); + } + + private void skipTopic(RichEditText editText) { +// Context context = editText.getContext(); +// if (context != null && mHost != null) +// TweetTopicActivity.show(mHost, editText); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/adapter/OnKeyArrivedListenerAdapterV2.java b/app/src/main/java/net/oschina/app/improve/widget/adapter/OnKeyArrivedListenerAdapterV2.java new file mode 100644 index 0000000000000000000000000000000000000000..2f18cbcc36e450b6ee60aae5ed120180b36fdcc0 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/adapter/OnKeyArrivedListenerAdapterV2.java @@ -0,0 +1,100 @@ +package net.oschina.app.improve.widget.adapter; + +import android.app.Activity; +import android.content.Context; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.Spanned; +import android.text.TextUtils; + +import net.oschina.app.improve.tweet.activities.TweetTopicActivity; +import net.oschina.app.improve.user.activities.UserSelectFriendsActivity; +import net.oschina.app.improve.widget.RichEditText; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author qiujuer Email:qiujuer@live.cn + * @version 1.0.0 + */ + +public class OnKeyArrivedListenerAdapterV2 implements RichEditText.OnKeyArrivedListener { + private Object mHost; + + public OnKeyArrivedListenerAdapterV2(Object host) { + if (host instanceof Activity || host instanceof Fragment || host instanceof android.app.Fragment) { + mHost = host; + } else { + throw new RuntimeException("Host 必须是 Activity/Fragment"); + } + } + + @Override + public boolean onMentionKeyArrived(RichEditText editText) { + Editable msg = editText.getText(); + String msgStr = msg.toString(); + if (TextUtils.isEmpty(msgStr)) + return false; + int selStartIndex = editText.getSelectionStart(); + + if (TextUtils.isEmpty(msgStr.trim()) || selStartIndex <= 0 + || TextUtils.isEmpty(msgStr.substring(selStartIndex - 1, selStartIndex).trim())) { + skipMention(editText); + } + + return true; + } + + @Override + public boolean onTopicKeyArrived(RichEditText editText) { + Editable msg = editText.getText(); + String msgStr = msg.toString(); + int selStartIndex = editText.getSelectionStart(); + int selEndIndex = editText.getSelectionEnd(); + + if (TextUtils.isEmpty(msgStr.trim()) || selStartIndex <= 0) { + skipTopic(editText); + return true; + } + + int startIndex = 0; + RichEditText.TagSpan[] spans = msg.getSpans(0, selStartIndex, RichEditText.TagSpan.class); + if (spans.length > 0) { + startIndex = msg.getSpanEnd(spans[spans.length - 1]); + } + + boolean isMatcher = false; + String tagStr = msgStr.substring(startIndex, selStartIndex) + "#"; + Pattern pattern = Pattern.compile(RichEditText.MATCH_TOPIC); + Matcher matcher = pattern.matcher(tagStr); + while (matcher.find()) { + String str = matcher.group(); + int matcherStart = matcher.start() + startIndex; + int matcherEnd = matcher.end() + startIndex; + if (matcherEnd == selStartIndex + 1) + msg.replace(selStartIndex, selEndIndex, "#"); + msg.setSpan(new RichEditText.TagSpan(str), matcherStart, matcherEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + isMatcher = true; + } + if (isMatcher) { + editText.setSelection(selEndIndex); + return false; + } + + skipTopic(editText); + return true; + } + + public void skipMention(RichEditText editText) { + Context context = editText.getContext(); + if (context != null && mHost != null) + UserSelectFriendsActivity.show(mHost, editText); + } + + public void skipTopic(RichEditText editText) { + Context context = editText.getContext(); + if (context != null && mHost != null) + TweetTopicActivity.show(mHost, editText); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/banner/EventHeaderView.java b/app/src/main/java/net/oschina/app/improve/widget/banner/EventHeaderView.java new file mode 100644 index 0000000000000000000000000000000000000000..1a5c390324cf002c98f0bc636e59a5f4e61efb9a --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/banner/EventHeaderView.java @@ -0,0 +1,45 @@ +package net.oschina.app.improve.widget.banner; + +import android.content.Context; +import android.view.ViewGroup; + +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.improve.widget.ViewEventBanner; + +/** + * Created by haibin + * on 2016/10/26. + */ + +public class EventHeaderView extends HeaderView { + public EventHeaderView(Context context, RequestManager loader, String api, String bannerCache) { + super(context, loader, api, bannerCache); + } + + @Override + protected int getLayoutId() { + return R.layout.layout_event_header_view; + } + + @Override + protected Object instantiateItem(ViewGroup container, int position) { + ViewEventBanner view = new ViewEventBanner(getContext()); + container.addView(view); + if (mBanners.size() != 0) { + int p = position % mBanners.size(); + if (p >= 0 && p < mBanners.size()) { + view.initData(mImageLoader, mBanners.get(p)); + } + } + return view; + } + + @Override + protected void destroyItem(ViewGroup container, int position, Object object) { + if (object instanceof ViewEventBanner) { + container.removeView((ViewEventBanner) object); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/banner/HeaderView.java b/app/src/main/java/net/oschina/app/improve/widget/banner/HeaderView.java new file mode 100644 index 0000000000000000000000000000000000000000..4676affaa3fc0dd72a8dceb76cd322b0a2cb933d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/banner/HeaderView.java @@ -0,0 +1,213 @@ +package net.oschina.app.improve.widget.banner; + +import android.content.Context; +import android.os.Handler; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RelativeLayout; + +import com.bumptech.glide.RequestManager; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.bean.Banner; +import net.oschina.app.improve.app.AppOperator; +import net.oschina.app.improve.bean.base.PageBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.utils.CacheManager; +import net.oschina.app.improve.widget.indicator.CirclePagerIndicator; + +import java.util.ArrayList; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * Created by haibin + * on 2016/10/26. + */ + +public abstract class HeaderView extends RelativeLayout implements ViewPager.OnPageChangeListener, Runnable { + protected ViewPager mViewPager; + protected CirclePagerIndicator mIndicator; + protected List mBanners; + protected BannerAdapter mAdapter; + protected Handler mHandler; + protected int mCurrentItem; + protected RequestManager mImageLoader; + protected TextHttpResponseHandler mCallBack; + protected String mUrl; + private boolean isScrolling; + protected String mBannerCache; + + public HeaderView(Context context, RequestManager loader, String api, String bannerCache) { + super(context); + mImageLoader = loader; + this.mUrl = api; + this.mBannerCache = bannerCache; + init(context); + } + + protected void init(Context context) { + mBanners = new ArrayList<>(); + List banners = CacheManager.readListJson(context, mBannerCache, Banner.class); + if (banners != null) { + mBanners.addAll(banners); + if (mHandler == null) + mHandler = new Handler(); + mHandler.postDelayed(this, 5000); + } + LayoutInflater.from(context).inflate(getLayoutId(), this, true); + mViewPager = (ViewPager) findViewById(R.id.vp_banner); + mIndicator = (CirclePagerIndicator) findViewById(R.id.indicator); + mAdapter = new BannerAdapter(); + mViewPager.addOnPageChangeListener(this); + mViewPager.setAdapter(mAdapter); + mIndicator.bindViewPager(mViewPager); + mIndicator.setCount(mBanners.size()); + + new SmoothScroller(getContext()).bingViewPager(mViewPager); + mViewPager.setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + isScrolling = true; + case MotionEvent.ACTION_UP: + isScrolling = false; + break; + case MotionEvent.ACTION_CANCEL: + isScrolling = false; + break; + case MotionEvent.ACTION_MOVE: + isScrolling = true; + break; + } + return false; + } + }); + mCallBack = new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + final ResultBean> result = AppOperator.createGson().fromJson(responseString, + new TypeToken>>() { + }.getType()); + if (result != null && result.isSuccess()) { + CacheManager.saveToJson(getContext(), mBannerCache, result.getResult().getItems()); + setBanners(result.getResult().getItems()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }; + requestBanner(); + } + + @Override + public void run() { + mHandler.postDelayed(this, 5000); + if (isScrolling) + return; + mCurrentItem = mCurrentItem + 1; + mViewPager.setCurrentItem(mCurrentItem); + } + + public void requestBanner() { + if (mHandler == null) + mHandler = new Handler(); + mHandler.removeCallbacks(this); + OSChinaApi.getBanner(mUrl, mCallBack); + } + + void setBanners(List banners) { + if (banners != null) { + mHandler.removeCallbacks(this); + mBanners.clear(); + mBanners.addAll(banners); + mViewPager.getAdapter().notifyDataSetChanged(); + mIndicator.setCount(mBanners.size()); + mIndicator.notifyDataSetChanged(); + if (mCurrentItem == 0 && mBanners.size() != 1) { + mCurrentItem = mBanners.size() * 1000; + mViewPager.setCurrentItem(mCurrentItem); + } + if (mBanners.size() > 1) { + mHandler.postDelayed(this, 5000); + } + } + } + + protected abstract int getLayoutId(); + + protected abstract Object instantiateItem(ViewGroup container, int position); + + protected abstract void destroyItem(ViewGroup container, int position, Object object); + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + isScrolling = mCurrentItem != position; + } + + @Override + public void onPageSelected(int position) { + isScrolling = false; + mCurrentItem = position; + } + + @Override + public void onPageScrollStateChanged(int state) { + isScrolling = state != ViewPager.SCROLL_STATE_IDLE; + } + + private class BannerAdapter extends PagerAdapter { + @Override + public int getCount() { + return mBanners.size() == 1 ? 1 : Integer.MAX_VALUE; + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + return HeaderView.this.instantiateItem(container, position); + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + HeaderView.this.destroyItem(container, position, object); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mHandler == null) + mHandler = new Handler(); + mHandler.postDelayed(this, 5000); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mHandler == null) + return; + mHandler.removeCallbacks(this); + mHandler = null; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/banner/NewsHeaderView.java b/app/src/main/java/net/oschina/app/improve/widget/banner/NewsHeaderView.java new file mode 100644 index 0000000000000000000000000000000000000000..1d31b4fa5814052eb08db0df0708a142bd3eb0b8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/banner/NewsHeaderView.java @@ -0,0 +1,75 @@ +package net.oschina.app.improve.widget.banner; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.bumptech.glide.RequestManager; + +import net.oschina.app.R; +import net.oschina.app.bean.Banner; +import net.oschina.app.improve.widget.ViewNewsBanner; + +import java.util.List; + +/** + * Created by haibin + * on 2016/10/26. + */ + +@SuppressLint("ViewConstructor") +public class NewsHeaderView extends HeaderView { + private TextView mTitleTextView; + + public NewsHeaderView(Context context, RequestManager loader, String api, String bannerCache) { + super(context, loader, api, bannerCache); + } + + @Override + protected void init(Context context) { + super.init(context); + mTitleTextView = (TextView) findViewById(R.id.tv_title); + } + + @Override + protected int getLayoutId() { + return R.layout.layout_news_header_view; + } + + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + if (mBanners.size() != 0) + mTitleTextView.setText(mBanners.get(position % mBanners.size()).getName()); + } + + @Override + void setBanners(List banners) { + super.setBanners(banners); + if (banners.size() > 0 && mCurrentItem == 0) { + mTitleTextView.setText(banners.get(0).getName()); + } + } + + @Override + protected Object instantiateItem(ViewGroup container, int position) { + ViewNewsBanner view = new ViewNewsBanner(getContext()); + container.addView(view); + if (mBanners.size() != 0) { + int p = position % mBanners.size(); + if (p >= 0 && p < mBanners.size()) { + view.initData(mImageLoader, mBanners.get(p)); + + } + } + return view; + } + + @Override + protected void destroyItem(ViewGroup container, int position, Object object) { + if (object instanceof ViewNewsBanner) { + container.removeView((ViewNewsBanner) object); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/banner/SmoothScroller.java b/app/src/main/java/net/oschina/app/improve/widget/banner/SmoothScroller.java new file mode 100644 index 0000000000000000000000000000000000000000..d3fc47ce3c495b97be64a36699e308c5489f5563 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/banner/SmoothScroller.java @@ -0,0 +1,40 @@ +package net.oschina.app.improve.widget.banner; + +import android.content.Context; +import android.support.v4.view.ViewPager; +import android.widget.Scroller; + +import java.lang.reflect.Field; + +/** + * Created by huanghaibin + * on 16-5-26. + */ +class SmoothScroller extends Scroller { + private int mDuration = 1200; // + + SmoothScroller(Context context) { + super(context); + } + + @Override + public void startScroll(int startX, int startY, int dx, int dy, int duration) { + super.startScroll(startX, startY, dx, dy, mDuration); + } + + @Override + public void startScroll(int startX, int startY, int dx, int dy) { + super.startScroll(startX, startY, dx, dy, mDuration); + } + + void bingViewPager(ViewPager viewPager) { + try { + Field mScroller; + mScroller = ViewPager.class.getDeclaredField("mScroller"); + mScroller.setAccessible(true); + mScroller.set(viewPager, this); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/indicator/CirclePagerIndicator.java b/app/src/main/java/net/oschina/app/improve/widget/indicator/CirclePagerIndicator.java new file mode 100644 index 0000000000000000000000000000000000000000..8190a7be6f2ef62443a73ede104deab4e5b67d5c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/indicator/CirclePagerIndicator.java @@ -0,0 +1,245 @@ +package net.oschina.app.improve.widget.indicator; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.View; + +import net.oschina.app.R; + + +/** + * Created by huanghaibin + * on 16-5-19. + */ +@SuppressWarnings("unused") +public class CirclePagerIndicator extends View implements PagerIndicator { + private float mRadius; + private float mIndicatorRadius; + private final Paint mPaintFill = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint mPaintStroke = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint mPaintIndicator = new Paint(Paint.ANTI_ALIAS_FLAG); + private ViewPager mViewPager; + private ViewPager.OnPageChangeListener mListener; + private int mCurrentPage; + private int mFollowPage; + private float mPageOffset; + private boolean mCenterHorizontal; + private boolean mIsFollow; + private float mIndicatorSpace; + + private int mCount; + + public CirclePagerIndicator(Context context) { + this(context, null); + } + + public CirclePagerIndicator(Context context, AttributeSet attrs) { + super(context, attrs); + + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CirclePagerIndicator); + + mCenterHorizontal = a.getBoolean(R.styleable.CirclePagerIndicator_circle_indicator_centerHorizontal, true); + mPaintFill.setStyle(Paint.Style.FILL); + mPaintFill.setColor(a.getColor(R.styleable.CirclePagerIndicator_circle_indicator_color, 0x0000ff)); + mPaintStroke.setStyle(Paint.Style.STROKE); + mPaintStroke.setColor(a.getColor(R.styleable.CirclePagerIndicator_circle_indicator_stroke_color, 0x000000)); + mPaintStroke.setStrokeWidth(a.getDimension(R.styleable.CirclePagerIndicator_circle_indicator_stroke_width, 0)); + mPaintIndicator.setStyle(Paint.Style.FILL); + mPaintIndicator.setColor(a.getColor(R.styleable.CirclePagerIndicator_circle_indicator_fill_color, 0x0000ff)); + mRadius = a.getDimension(R.styleable.CirclePagerIndicator_circle_indicator_radius, 10); + mIndicatorSpace = a.getDimension(R.styleable.CirclePagerIndicator_circle_indicator_space, 20); + mIndicatorRadius = a.getDimension(R.styleable.CirclePagerIndicator_circle_indicator_indicator_radius, 10); + mIsFollow = a.getBoolean(R.styleable.CirclePagerIndicator_circle_indicator_follow, false); + if (mIndicatorRadius < mRadius) mIndicatorRadius = mRadius; + a.recycle(); + } + + @Override + protected void onDraw(Canvas canvas) { + + super.onDraw(canvas); + + if (mViewPager == null) { + return; + } + final int count = mCount; + if (count == 0) { + return; + } + + if (mCurrentPage >= count) { + setCurrentItem(count - 1); + return; + } + + int width = getWidth(); + int paddingLeft = getPaddingLeft(); + int paddingRight = getPaddingRight(); + int paddingTop = getPaddingTop(); + + final float circleAndSpace = 2 * mRadius + mIndicatorSpace;//直径+圆的间隔 + final float yOffset = getHeight() / 2;//竖直方向圆心偏移量,剧中对齐 + float xOffset = paddingLeft + mRadius;//水平方向圆心偏移量 + + //如果采用水平居中对齐 + if (mCenterHorizontal) { + //xOffset += ((width - paddingLeft - paddingRight) - (count * circleAndSpace)) / 2.0f; + xOffset = (width - count * 2 * mRadius - (count - 1) * mIndicatorSpace) / 2 - mRadius; + } + + float cX; + float cY; + + float strokeRadius = mRadius; + //如果绘制外圆 + if (mPaintStroke.getStrokeWidth() > 0) { + strokeRadius -= mPaintStroke.getStrokeWidth() / 2.0f; + } + + //绘制所有圆点 + for (int i = 0; i < count; i++) { + + cX = xOffset + (i * circleAndSpace);//计算下个圆绘制起点偏移量 + cY = yOffset; + + //绘制圆 + if (mPaintFill.getAlpha() > 0) { + canvas.drawCircle(cX, cY, strokeRadius, mPaintFill); + } + + //绘制外圆 + if (strokeRadius != mRadius) { + canvas.drawCircle(cX, cY, mRadius, mPaintStroke); + } + } + + float cx = (!mIsFollow ? mFollowPage : mCurrentPage) * circleAndSpace; + + //指示器选择缓慢移动 + if (mIsFollow) { + cx += mPageOffset * circleAndSpace; + } + + cX = xOffset + cx; + cY = yOffset; + canvas.drawCircle(cX, cY, mIndicatorRadius, mPaintIndicator); + } + + public void setCount(int count) { + this.mCount = count; + } + + @Override + public void bindViewPager(ViewPager view) { + if (mViewPager == view) { + return; + } + if (view.getAdapter() == null) { + throw new IllegalStateException("ViewPager does not set adapter"); + } + mViewPager = view; + mViewPager.addOnPageChangeListener(this); + invalidate(); + } + + @Override + public void bindViewPager(ViewPager view, int initialPosition) { + bindViewPager(view); + setCurrentItem(initialPosition); + } + + @Override + public void setCurrentItem(int item) { + if (mViewPager == null) { + throw new IllegalStateException("indicator has not bind ViewPager"); + } + mViewPager.setCurrentItem(item); + mCurrentPage = item; + invalidate(); + } + + @Override + public void notifyDataSetChanged() { + invalidate(); + requestLayout(); + } + + @Override + public void onPageScrollStateChanged(int state) { + if (mListener != null) { + mListener.onPageScrollStateChanged(state); + } + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + mCurrentPage = mCount == 0 ? mCount : position % mCount; + mPageOffset = positionOffset; + //如果指示器跟随ViewPager缓慢滑动,那么滚动的时候都绘制界面 + if (mIsFollow) { + invalidate(); + } + if (mListener != null) { + mListener.onPageScrolled(position, positionOffset, positionOffsetPixels); + } + } + + @Override + public void onPageSelected(int position) { + mCurrentPage = mCount == 0 ? mCount : position % mCount; + mFollowPage = mCount == 0 ? mCount : position % mCount; + invalidate(); + if (mListener != null) { + mListener.onPageSelected(position); + } + } + + @Override + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { + mListener = listener; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); + } + + private int measureWidth(int measureSpec) { + int width; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) { + width = specSize; + } else { + final int count = mCount; + width = (int) (getPaddingLeft() + getPaddingRight() + + (count * 2 * mRadius) + (mIndicatorRadius - mRadius) * 2 + (count - 1) * mIndicatorSpace); + if (specMode == MeasureSpec.AT_MOST) { + width = Math.min(width, specSize); + } + } + return width; + } + + private int measureHeight(int measureSpec) { + int height; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + height = specSize; + } else { + height = (int) (2 * mRadius + getPaddingTop() + getPaddingBottom() + 1); + if (specMode == MeasureSpec.AT_MOST) { + height = Math.min(height, specSize); + } + } + return height; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/indicator/LinePagerIndicator.java b/app/src/main/java/net/oschina/app/improve/widget/indicator/LinePagerIndicator.java new file mode 100644 index 0000000000000000000000000000000000000000..7b3c5f518bf38345382c9788f06d06ef06c64545 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/indicator/LinePagerIndicator.java @@ -0,0 +1,226 @@ +package net.oschina.app.improve.widget.indicator; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.View; + +import net.oschina.app.R; + + +/** + * Created by huanghaibin + * on 16-5-19. + */ +@SuppressWarnings("unused") +public class LinePagerIndicator extends View implements PagerIndicator { + + //绘制Line的画笔 + private final Paint mPaintFull = new Paint(Paint.ANTI_ALIAS_FLAG); + + //绘制Line指示器当前位置的画笔 + private final Paint mPaintIndicator = new Paint(Paint.ANTI_ALIAS_FLAG); + + private ViewPager mViewPager; + private ViewPager.OnPageChangeListener mListener; + private float mPageOffset; + private int mCurrentPage; + private int mFollowPage; + private boolean mCenterHorizontal; + private float mLineWidth; + private float mLineHeight; + private float mLineIndicatorHeight; + private float mSpace; + private boolean mIsFollow; + + + public LinePagerIndicator(Context context) { + this(context, null); + } + + @SuppressWarnings("SuspiciousNameCombination") + public LinePagerIndicator(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LinePagerIndicator); + mCenterHorizontal = a.getBoolean(R.styleable.LinePagerIndicator_line_indicator_centerHorizontal, true); + mLineWidth = a.getDimension(R.styleable.LinePagerIndicator_line_indicator_width, 10); + mSpace = a.getDimension(R.styleable.LinePagerIndicator_line_indicator_space, 5); + mLineHeight = a.getDimension(R.styleable.LinePagerIndicator_line_indicator_height, 0); + mLineIndicatorHeight = a.getDimension(R.styleable.LinePagerIndicator_line_indicator_indicator_height, mLineHeight); + mPaintFull.setColor(a.getColor(R.styleable.LinePagerIndicator_line_indicator_fill_color, 0x000000)); + mPaintIndicator.setColor(a.getColor(R.styleable.LinePagerIndicator_line_indicator_indicator_color, 0x00ff00)); + mIsFollow = a.getBoolean(R.styleable.LinePagerIndicator_line_indicator_follow, true); + a.recycle(); + + mPaintIndicator.setStrokeWidth(mLineIndicatorHeight > mLineHeight ? mLineIndicatorHeight : mLineHeight); + mPaintFull.setStrokeWidth(mLineHeight); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (mViewPager == null) { + return; + } + + final int count = mViewPager.getAdapter().getCount(); + if (count == 0) { + return; + } + + if (mCurrentPage >= count) { + setCurrentItem(count - 1); + return; + } + + final float lineWidthAndSpace = mLineWidth + mSpace;//线宽和间距 + final float indicatorWidth = (count * lineWidthAndSpace) - mSpace;//指示器+间隔 + + final float paddingTop = getPaddingTop(); + final float paddingLeft = getPaddingLeft(); + final float paddingRight = getPaddingRight(); + + float verticalOffset = paddingTop + ((getHeight() - paddingTop - getPaddingBottom()) / 2.0f);//绘制线的中心竖直方向偏移量 + float horizontalOffset = paddingLeft;////绘制线的中心水平方向偏移量 + + //如果采用水平居中对齐的水平偏移量 + if (mCenterHorizontal) { + horizontalOffset += ((getWidth() - paddingLeft - paddingRight) - indicatorWidth) / 2.0f; + } + + float startX; + float stopX; + + for (int i = 0; i < count; i++) { + startX = horizontalOffset + (i * lineWidthAndSpace);////计算下个圆绘制起点偏移量 + stopX = startX + mLineWidth; + canvas.drawLine(startX, verticalOffset, stopX, verticalOffset, mPaintFull); + } + + + float currentSpace = (!mIsFollow ? mFollowPage : mCurrentPage) * lineWidthAndSpace; + + + //指示器选择缓慢移动 + if (mIsFollow) { + currentSpace += mPageOffset * lineWidthAndSpace; + } + + startX = horizontalOffset + currentSpace; + stopX = startX + mLineWidth; + canvas.drawLine(startX, verticalOffset, stopX, verticalOffset, mPaintIndicator); + } + + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); + } + + + private int measureWidth(int measureSpec) { + float width; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) { + width = specSize; + } else { + final int count = mViewPager.getAdapter().getCount(); + width = getPaddingLeft() + getPaddingRight() + (count * mLineWidth) + ((count - 1) * mSpace); + if (specMode == MeasureSpec.AT_MOST) { + width = Math.min(width, specSize); + } + } + return (int) Math.ceil(width); + } + + private int measureHeight(int measureSpec) { + float height; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + if (specMode == MeasureSpec.EXACTLY) { + height = specSize; + } else { + height = mPaintIndicator.getStrokeWidth() + getPaddingTop() + getPaddingBottom(); + if (specMode == MeasureSpec.AT_MOST) { + height = Math.min(height, specSize); + } + } + return (int) Math.ceil(height); + } + + @Override + public void bindViewPager(ViewPager viewPager) { + if (mViewPager == viewPager) { + return; + } + if (viewPager.getAdapter() == null) { + throw new IllegalStateException("ViewPager does not set adapter"); + } + mViewPager = viewPager; + mViewPager.addOnPageChangeListener(this); + invalidate(); + } + + @Override + public void bindViewPager(ViewPager view, int initialPosition) { + bindViewPager(view); + setCurrentItem(initialPosition); + } + + @Override + public void setCurrentItem(int item) { + if (mViewPager == null) { + throw new IllegalStateException("indicator has not bind ViewPager"); + } + mViewPager.setCurrentItem(item); + mCurrentPage = item; + invalidate(); + } + + @Override + public void notifyDataSetChanged() { + invalidate(); + } + + @Override + public void onPageScrollStateChanged(int state) { + if (mListener != null) { + mListener.onPageScrollStateChanged(state); + } + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + mCurrentPage = position; + mPageOffset = positionOffset; + //如果指示器跟随ViewPager缓慢滑动,那么滚动是时候都绘制界面 + if (mIsFollow) { + invalidate(); + } + if (mListener != null) { + mListener.onPageScrolled(position, positionOffset, positionOffsetPixels); + } + } + + @Override + public void onPageSelected(int position) { + mCurrentPage = position; + mFollowPage = position; + invalidate(); + if (mListener != null) { + mListener.onPageSelected(position); + } + } + + @Override + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { + mListener = listener; + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/widget/indicator/PagerIndicator.java b/app/src/main/java/net/oschina/app/improve/widget/indicator/PagerIndicator.java new file mode 100644 index 0000000000000000000000000000000000000000..bbdfe52ff73b8e2fa68d50b8c3a9da7696b5005b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/indicator/PagerIndicator.java @@ -0,0 +1,48 @@ +package net.oschina.app.improve.widget.indicator; + +import android.support.v4.view.ViewPager; + +/** + * Created by huanghaibin + * on 16-5-19. + * 抽象指示器 + */ +@SuppressWarnings("unused") +public interface PagerIndicator extends ViewPager.OnPageChangeListener { + + /** + * bind the viewPager into indicator + * + * @param viewPager the ViewPager + */ + void bindViewPager(ViewPager viewPager); + + + /** + * bind the viewPager into indicator + * + * @param viewPager the ViewPager + * @param initialPosition initialPosition + */ + void bindViewPager(ViewPager viewPager, int initialPosition); + + + /** + * the ViewPager Current Item + * + * @param currentItem currentItem + */ + void setCurrentItem(int currentItem); + + /** + * the ViewPager ChangeListener + * + * @param listener listener + */ + void setOnPageChangeListener(ViewPager.OnPageChangeListener listener); + + /** + * update the DataSet,invalidate + */ + void notifyDataSetChanged(); +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/indicator/TrianglePagerIndicator.java b/app/src/main/java/net/oschina/app/improve/widget/indicator/TrianglePagerIndicator.java new file mode 100644 index 0000000000000000000000000000000000000000..32bc094e353c9c381b93bc87b8a69c2ae5724f69 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/indicator/TrianglePagerIndicator.java @@ -0,0 +1,81 @@ +package net.oschina.app.improve.widget.indicator; + +import android.content.Context; +import android.graphics.Paint; +import android.graphics.Path; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +import java.util.List; + +/** + * Created by huanghaibin + * on 16-6-15. + */ +public class TrianglePagerIndicator extends LinearLayout implements PagerIndicator { + + private static final int MAX_TRIANGLE_WIDTH = 130; + private static final int MAX_TRIANGLE_HEIGHT = 50; + + private static final int TEXT_COLOR_NORMAL = 0xFFF6F6F6; + private static final int TEXT_COLOR_SELECT = 0x00000000; + private static final int INDICATOR_COLOR_NORMAL = 0xFFF6F6F6; + private static final int INDICATOR_COLOR_SELECT = 0x00000000; + + private Paint mPaint = new Paint(); + private Path mPath = new Path(); + + private int mTriangleWidth; + private int mTriangleHeight; + + private List tabTitles; + + public TrianglePagerIndicator(Context context) { + super(context); + } + + public TrianglePagerIndicator(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void bindViewPager(ViewPager viewPager) { + + } + + @Override + public void bindViewPager(ViewPager viewPager, int initialPosition) { + + } + + @Override + public void setCurrentItem(int currentItem) { + + } + + @Override + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { + + } + + @Override + public void notifyDataSetChanged() { + + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + + } + + @Override + public void onPageScrollStateChanged(int state) { + + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/rich/ImagePanel.java b/app/src/main/java/net/oschina/app/improve/widget/rich/ImagePanel.java new file mode 100644 index 0000000000000000000000000000000000000000..764d6339806036e0d921369fc87be5d84899762c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/rich/ImagePanel.java @@ -0,0 +1,162 @@ +package net.oschina.app.improve.widget.rich; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.Rect; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; + +import net.oschina.app.R; + +/** + * 图片输入面板 + * Created by huanghaibin on 2017/8/3. + */ + +@SuppressWarnings("unused") +public class ImagePanel extends FrameLayout implements + View.OnClickListener, View.OnLongClickListener { + private ImageView mImageView; + private RichLinearLayout mParent; + public boolean isDeleteMode; + String mImagePath; + public ImagePanel(@NonNull Context context) { + this(context, null); + } + public ImagePanel(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + LayoutInflater.from(context).inflate(R.layout.view_image_panel, this, true); + mImageView = (ImageView) findViewById(R.id.iv_image); + setOnClickListener(this); + setOnLongClickListener(this); + } + void setImagePath(String imagePath) { + mImagePath = imagePath; + Glide.with(getContext()) + .load(imagePath) + .asBitmap() + .fitCenter() + .listener(new RequestListener() { + @Override + public boolean onException(Exception e, String model, Target target, boolean isFirstResource) { + return false; + } + @Override + public boolean onResourceReady(Bitmap resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { + return false; + } + }) + .into(mImageView); + } + /** + * 显示编辑删除模式 + * + * @param index View下标 + */ + void showDeleteMode(int index) { + isDeleteMode = true; + showMode(true); + } + void showMode(boolean isDelete) { + if (isDelete) { + setBackgroundResource(R.drawable.bg_image_panel); + int p = UI.dipToPx(getContext(), 1); + setPadding(p, p, p, p); + } else { + setBackgroundColor(Color.WHITE); + isDeleteMode = false; + setPadding(0, 0, 0, 0); + } + } + void clearMode() { + setFocusable(false); + setFocusableInTouchMode(false); + clearFocus(); + } + void setFocusMode() { + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + } + @Override + public void onClick(View v) { + } + @Override + public boolean onLongClick(final View v) { + //mParent.mFocusView.setFocusable(false); + //mParent.mFocusView.setFocusableInTouchMode(false); + //mParent.mFocusView.clearFocus(); + mParent.mParent.mParent.mContentPanel.setVisibility(GONE); + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + if (mParent != null && + mParent.mParent != null && + !mParent.mParent.isKeyboardOpen()) { + openKeyboard(v); + mParent.mFocusPanel = this; + } + return true; + } + @Override + protected void onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect) { + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + isDeleteMode = false; + if (gainFocus) { + showMode(true); + } else { + showMode(false); + } + } + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + Log.e("onKeyDown", "onKeyDown"); + switch (keyCode) { + case KeyEvent.KEYCODE_DEL: + mParent.adjustLayout(this, true); + break; + case KeyEvent.KEYCODE_ENTER: + if (mParent.mFocusPanel != null) { + mParent.mFocusPanel.clearMode(); + } + clearMode(); + mParent.adjustLayout(this, false); + return false; + } + return super.onKeyDown(keyCode, event); + } + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (mParent == null) { + mParent = (RichLinearLayout) getParent(); + } + } + String getFileName() { + return mImagePath.substring(mImagePath.lastIndexOf("/") + 1); + } + private void openKeyboard(View view) { + //view.setFocusable(true); + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + assert imm != null; + imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); + } + private boolean isOpenKeyboard(View view) { + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + assert imm != null; + return imm.isActive(); + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/widget/rich/ImageSection.java b/app/src/main/java/net/oschina/app/improve/widget/rich/ImageSection.java new file mode 100644 index 0000000000000000000000000000000000000000..b2cb60fbd3d451085dad7c6c48877942440785f4 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/rich/ImageSection.java @@ -0,0 +1,45 @@ +package net.oschina.app.improve.widget.rich; + +/** + * 图片段落 + * Created by huanghaibin on 2017/8/3. + */ + +public class ImageSection extends Section { + private static final long serialVersionUID = -91787654415366445L; + + private String fileName; + + private int height; + + private int width; + + private String filePath; + public ImageSection() { + type = TYPE_IMAGE; + } + public int getHeight() { + return height; + } + public void setHeight(int height) { + this.height = height; + } + public int getWidth() { + return width; + } + public void setWidth(int width) { + this.width = width; + } + public String getFileName() { + return fileName; + } + public void setFileName(String fileName) { + this.fileName = fileName; + } + public String getFilePath() { + return filePath; + } + public void setFilePath(String filePath) { + this.filePath = filePath; + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/widget/rich/RichBar.java b/app/src/main/java/net/oschina/app/improve/widget/rich/RichBar.java new file mode 100644 index 0000000000000000000000000000000000000000..df25639df1dd3cb18c54f6531d658f2c2ba93f93 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/rich/RichBar.java @@ -0,0 +1,46 @@ +package net.oschina.app.improve.widget.rich; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.ImageButton; +import android.widget.LinearLayout; + +import net.oschina.app.R; + +/** + * 底部工具栏 + * Created by huanghaibin on 2017/8/3. + */ + + +public class RichBar extends LinearLayout { + ImageButton mBtnKeyboard; + ImageButton mBtnFont; + ImageButton mBtnAlign; + ImageButton mBtnCategory; + ImageButton mBtnTitle; + + public RichBar(Context context) { + this(context, null); + } + + public RichBar(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + LayoutInflater.from(getContext()).inflate(R.layout.view_rich_bar, this, true); + mBtnKeyboard = (ImageButton) findViewById(R.id.btn_keyboard); + mBtnFont = (ImageButton) findViewById(R.id.btn_font); + mBtnAlign = (ImageButton) findViewById(R.id.btn_align); + mBtnCategory = (ImageButton) findViewById(R.id.btn_category); + mBtnTitle = (ImageButton)findViewById(R.id.btn_h); + } + + void setBarEnable(boolean isEnable){ + mBtnKeyboard.setEnabled(isEnable); + mBtnFont.setEnabled(isEnable); + mBtnAlign.setEnabled(isEnable); + mBtnCategory.setEnabled(isEnable); + mBtnTitle.setEnabled(isEnable); + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/widget/rich/RichEditLayout.java b/app/src/main/java/net/oschina/app/improve/widget/rich/RichEditLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..f1a830f957f1786a78afe986bcb9d89ff1a5ff51 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/rich/RichEditLayout.java @@ -0,0 +1,201 @@ +package net.oschina.app.improve.widget.rich; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.graphics.Rect; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.LinearLayout; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * 富文本编辑器入口,包含底部工具栏 + * Created by huanghaibin on 2017/8/3. + */ + +@SuppressWarnings("unused") +public class RichEditLayout extends LinearLayout { + private RichScrollView mScrollView; + RichBar mRichBar; + View mContentPanel;//底部内容布局 + boolean isKeyboardOpen = true; + public static int KEYBOARD_HEIGHT = 0; + + public RichEditLayout(Context context) { + this(context, null); + } + + public RichEditLayout(Context context, AttributeSet attrs) { + super(context, attrs); + setOrientation(VERTICAL); + mScrollView = new RichScrollView(context); + LinearLayout.LayoutParams params = + new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f); + mScrollView.setLayoutParams(params); + addView(mScrollView); + mRichBar = new RichBar(getContext()); + LinearLayout.LayoutParams richParams = + new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + mRichBar.setLayoutParams(richParams); + addView(mRichBar); + +// if (KEYBOARD_HEIGHT != 0) { +// mContentPanel.getLayoutParams().height = Keyboard.KEYBOARD_HEIGHT; +// } + final Activity activity = (Activity) context; + final View rootView = activity.getWindow().getDecorView(); + rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + Rect r = new Rect(); + activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r); + int screenHeight = rootView.getHeight(); + int heightDiff = screenHeight - (r.bottom - r.top); + int statusBarHeight = 0; + if (heightDiff > 200) { + try { + @SuppressLint("PrivateApi") + Class c = Class.forName("com.android.internal.R$dimen"); + Object obj = c.newInstance(); + Field field = c.getField("status_bar_height"); + int x = Integer.parseInt(field.get(obj).toString()); + statusBarHeight = getResources().getDimensionPixelSize(x); + } catch (Exception e) { + e.printStackTrace(); + } + int h = heightDiff - statusBarHeight; + if (KEYBOARD_HEIGHT < h) { + KEYBOARD_HEIGHT = h; + if (mContentPanel != null) + mContentPanel.getLayoutParams().height = KEYBOARD_HEIGHT; + } + } + } + }); + } + + public void insertImagePanel(String image) { + mScrollView.addImagePanel(image); + } + + public void setContentPanel(View view) { + this.mContentPanel = view; + } + + public void setBold(boolean isBold) { + mScrollView.mRichLinearLayout.setBold(isBold); + } + + public void setItalic(boolean isItalic) { + mScrollView.mRichLinearLayout.setItalic(isItalic); + } + + public void setMidLine(boolean isMidLine) { + mScrollView.mRichLinearLayout.setMidLine(isMidLine); + } + + public void setAlignStyle(int align) { + mScrollView.mRichLinearLayout.setAlignStyle(align); + } + + public void init(List

    sections) { + mScrollView.mRichLinearLayout.init(sections); + } + + public void setColorSpan(String color) { + mScrollView.mRichLinearLayout.setColorSpan(color.replace("#", "")); + } + + public void setTextSizeSpan(boolean isIncrease) { + mScrollView.mRichLinearLayout.setTextSizeSpan(isIncrease); + } + + public void setTextSize(int size) { + mScrollView.mRichLinearLayout.setTextSize(size); + } + + public boolean isKeyboardOpen() { + return (mRichBar.getBottom() < UI.getScreenHeight(getContext()) - UI.dipToPx(getContext(), 80) + && mContentPanel.getVisibility() == GONE) || isKeyboardOpen; + } + + public void setKeyboardOpen(boolean isOpen) { + this.isKeyboardOpen = isOpen; + } + + public List createSectionList() { + return mScrollView.createSectionList(); + } + + public int getImageCount() { + return mScrollView.mRichLinearLayout.getImageCount(); + } + + public void openKeyboard() { + isKeyboardOpen = true; + DrawableCompat.setTint(mRichBar.mBtnKeyboard.getDrawable(), 0xff24cf5f); + mScrollView.mRichLinearLayout.mFocusView.setFocusable(true); + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) + imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); + } + + + public void closeKeyboard() { + isKeyboardOpen = false; + DrawableCompat.setTint(mRichBar.mBtnKeyboard.getDrawable(), 0xff111111); + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) + imm.hideSoftInputFromWindow(mScrollView.mRichLinearLayout.mFocusView.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN); + } + + public void setOnSectionChangeListener(RichEditText.OnSectionChangeListener listener) { + mScrollView.mRichLinearLayout.mListener = listener; + mScrollView.mRichLinearLayout.mFocusView.mListener = listener; + } + + public void setAdjustResize() { + Window window = ((Activity) getContext()).getWindow(); + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + } + + public void setAdjustNothing() { + Window window = ((Activity) getContext()).getWindow(); + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); + } + + public void setFontTint(int color) { + DrawableCompat.setTint(mRichBar.mBtnFont.getDrawable(), color); + } + + public void setCategoryTint(int color) { + DrawableCompat.setTint(mRichBar.mBtnCategory.getDrawable(), color); + } + + + public void setKeyboardTint(int color) { + DrawableCompat.setTint(mRichBar.mBtnKeyboard.getDrawable(), color); + } + + public String getTitle() { + return mScrollView.mRichLinearLayout.getTitle(); + } + + public String getSummary() { + return mScrollView.mRichLinearLayout.getSummary(); + } + + public boolean isEmpty() { + return TextUtils.isEmpty(mScrollView.mRichLinearLayout.mFocusView.getText().toString().trim()); + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/widget/rich/RichEditText.java b/app/src/main/java/net/oschina/app/improve/widget/rich/RichEditText.java new file mode 100644 index 0000000000000000000000000000000000000000..1feb17efd65acb230fd4e0443858595151dabd82 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/rich/RichEditText.java @@ -0,0 +1,1047 @@ +package net.oschina.app.improve.widget.rich; + +import android.annotation.SuppressLint; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Typeface; +import android.support.v7.widget.AppCompatEditText; +import android.text.Editable; +import android.text.Layout; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.AlignmentSpan; +import android.text.style.CharacterStyle; +import android.text.style.ForegroundColorSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.widget.EditText; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 墨记富文本编辑器 + * 需要实现的功能如下: + * 1:样式列表必须要和文本同步,这是为了复制样式 + * 2: 当两个EditText合并时,双方继承样式 + *

    + * 做法,删除文本的时候,需要合并段落,例如第二行从起点删除,合并到第一个段落,样式去掉,使用第一个段落的样式 + * 选择多个文本删除的时候,直接重新排版 + *

    + * Created by huanghaibin on 2017/8/3. + */ +@SuppressLint("ViewConstructor") +@SuppressWarnings("unused") +public class RichEditText extends AppCompatEditText implements TextWatcher, View.OnKeyListener, View.OnClickListener { + private RichLinearLayout mParent; + private boolean isMultiSelection;//多选模式 + private boolean isEnter, isDelete,isMultiDelete; + static final int INDEX_START = 0; + static final int INDEX_MID = 1; + static final int INDEX_END = 2; + OnSectionChangeListener mListener; + private boolean isDelaying; + /** + * 段落列表 + */ + List mSections; + + public RichEditText(Context context, OnSectionChangeListener listener) { + super(context); + mListener = listener; + mSections = new ArrayList<>(); + addTextChangedListener(this); + setBackgroundColor(Color.TRANSPARENT); + setOnKeyListener(this); + mSections.add(getDefaultSection(context)); + //clearKeyboard(); + } + + private TextSection getDefaultSection(Context context) { + TextSection defaultSection = new TextSection(); + defaultSection.setBold(false); + defaultSection.setTextSize(18); + defaultSection.setItalic(false); + defaultSection.setText(""); + defaultSection.setAlignment(TextSection.LEFT); + defaultSection.setColorHex("111111"); + return defaultSection; + } + + /** + * 清除软键盘,不弹出 + */ + private void clearKeyboard() { + try { + Class cls = EditText.class; + Method setSoftInputShownOnFocus; + setSoftInputShownOnFocus = cls.getMethod("setShowSoftInputOnFocus", boolean.class); + setSoftInputShownOnFocus.setAccessible(true); + setSoftInputShownOnFocus.invoke(this, false); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 光标改变 + * + * @param selStart 开始位置 + * @param selEnd selEnd + */ + @Override + protected void onSelectionChanged(int selStart, int selEnd) { + super.onSelectionChanged(selStart, selEnd); + isMultiSelection = (selEnd - selStart >= 1); + if (isMultiSelection) + return; + int index = getSelectionIndexFromStart(selStart); + if (mListener != null && index < mSections.size()) { + mListener.onSectionChange(mSections.get(index)); + } + } + + @Override + public void onClick(View v) { + } + + /** + * 文本变化之前发生 + * + * @param s 当前的文本 + * @param start 输入光标的起点 + * @param count 基本不用管 + * @param after 此次输入的多少字符串 + */ + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + //Log.e("beforeTextChanged", "" + s + " -- " + start + " -- " + count + " -- " + after + " -- " + getSelectionStart()); + if (isMultiSelection && isDelete && !isMultiDelete) {//调整段落 + adjustSection(s.toString(), start, count); + } + } + + /** + * 文本变化后 + * + * @param s 变化后的文本 + */ + @Override + public void afterTextChanged(Editable s) { + //Log.e("afterTextChanged", " -- " + s.toString()); + isDelete = false; + isPaste = false; + isMultiDelete = false; + } + + /** + * 文本正在输入 + * + * @param s 当前的字符 + * @param start 输入的光标位置 + * @param before 不用理 + * @param count 输入的量 + */ + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (mSections == null || isPaste) + return; + int index = getSelectionIndex();//获取当前段落位置 + //Log.e("onTextChanged", "" + s + " -- " + start + " -- " + before + " -- " + count + " -- " + mSections.size() + " -- " + index); + if (index >= 0 && index < mSections.size()) { + TextSection section = mSections.get(index); + setSectionStyle(section); + } + if (!isEnter) + return; + int preIndex = index - 1; + if (preIndex >= 0 && preIndex < mSections.size()) { + TextSection section = mSections.get(preIndex); + setColorSpan(Color.parseColor(("#" + section.getColorHex())), preIndex); + setBold(section.isBold(), preIndex); + setItalic(section.isItalic(), preIndex); + setMidLine(section.isMidLine(), preIndex); + setAlignStyle(section.getAlignment(), preIndex); + } + isEnter = false; + } + + private boolean isPaste; + + @Override + public boolean onTextContextMenuItem(int id) { + if (id == android.R.id.paste) {//拦截复制事件,清除复制文本的style + ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + if (clipboard != null && clipboard.hasPrimaryClip()) { + ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0); + if (item != null) { + String paste = item.coerceToText(getContext()).toString().trim(); + isPaste = true; + int selectionStart = getSelectionStart(); + int selectionEnd = getSelectionEnd(); + int indexStart = getSelectionIndexFromStart(selectionStart);//光标起始段落 + if (indexStart >= 0 && indexStart < mSections.size()) { + TextSection section = mSections.get(indexStart); + if (paste.contains("\n")) {//来自粘贴有换行的情况  + String[] selections = paste.split("\n", -1); + if (isMultiSelection || selectionStart < selectionEnd) {//如果是多选,注意头和尾之间的style段落都会被清除 + int indexEnd = getSelectionIndexFromStart(selectionEnd);//光标结束位置 + if (indexStart < indexEnd) { + List sections = new ArrayList<>(); + for (int i = indexStart + 1; i <= indexEnd; i++) { + sections.add(mSections.get(i)); + } + mSections.removeAll(sections); + } + getText().replace(selectionStart, selectionEnd, paste); + setSectionStyle(section, indexStart); + int length = selections.length; + for (int i = 1; i < length; i++) { + TextSection clone = section.cloneTextSelection(); + setSectionStyle(clone, indexStart + i); + mSections.add(indexStart, clone); + } + } else {//如果是单选,则直接插入 + getText().replace(selectionStart, selectionEnd, paste); + setSectionStyle(section, indexStart); + for (int i = 1; i < selections.length; i++) { + TextSection clone = section.cloneTextSelection(); + mSections.add(indexStart, clone); + setSectionStyle(clone, indexStart + i); + } + } + } else {//没有换行则不管 + getText().replace(selectionStart, selectionEnd, paste); + setSectionStyle(section); + } + } + } + return true; + } + } + if (id == android.R.id.cut) {//剪切 + isPaste = true; + int selectionStart = getSelectionStart(); + int selectionEnd = getSelectionEnd(); + final int indexStart = getSelectionIndexFromStart(selectionStart);//光标起始段落 + if (indexStart >= 0 && indexStart < mSections.size()) { + final TextSection section = mSections.get(indexStart); + if (isMultiSelection || selectionStart < selectionEnd) {//如果是多选,注意头和尾之间的style段落都会被清除 + int indexEnd = getSelectionIndexFromStart(selectionEnd);//光标结束位置 + if (indexStart < indexEnd) { + List sections = new ArrayList<>(); + for (int i = indexStart + 1; i <= indexEnd; i++) { + sections.add(mSections.get(i)); + } + mSections.removeAll(sections); + } + getText().delete(selectionStart, selectionEnd); + setSectionStyle(section, indexStart); + return true; + } + } + } + return super.onTextContextMenuItem(id); + } + + /** + * 情况就是开启多选模式 + * 调整段落 + * 合并style + * 逻辑: + */ + private void adjustSection(String s, int start, int count) { + int startSection = getSectionIndex(start); + int endSection = getSectionIndex(start + count); + if (startSection == endSection) + return; + List removeSections = new ArrayList<>(); + for (int i = startSection + 1; i <= endSection; i++) { + removeSections.add(mSections.get(i)); + } + mSections.removeAll(removeSections); + //最后一个要合并 + TextSection section = mSections.get(startSection); + setSectionStyle(section); + } + + private void setSectionStyle(TextSection section) { + setColorSpan(Color.parseColor(("#" + section.getColorHex()))); + setBold(section.isBold()); + setItalic(section.isItalic()); + setMidLine(section.isMidLine()); + setAlignStyle(section.getAlignment()); + setTextSizeSpan(section.getTextSize()); + + } + + void setSectionStyle(TextSection section, int index) { + setColorSpan(Color.parseColor(("#" + section.getColorHex())), index); + setBold(section.isBold(), index); + setItalic(section.isItalic(), index); + setMidLine(section.isMidLine(), index); + setAlignStyle(section.getAlignment(), index); + setTextSizeSpan(section.getTextSize(), index); + + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + mParent.mFocusView = this; + if (mParent.mFocusPanel != null) { + mParent.mFocusPanel.showMode(false); + } + //mParent.mParent.mParent.setAdjustNothing(); + if (mParent.mParent.mParent.mContentPanel.getVisibility() == VISIBLE) { + mParent.mParent.mParent.setAdjustNothing(); + } + mParent.mParent.mParent.isKeyboardOpen = true; + if (isDelaying) { + return super.onTouchEvent(event); + } + isDelaying = true; + postDelayed(new Runnable() { + @Override + public void run() { + isDelaying = false; + mParent.mParent.mParent.mContentPanel.setVisibility(GONE); + mParent.mParent.mParent.setAdjustResize(); + } + }, 500); + return super.onTouchEvent(event); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_DEL: + isDelete = true; + if (getSelectionStart() == 0 && !isMultiSelection) { + mParent.delete(this); + return true; + } + if (!isMultiSelection && isDeleteSection() && !TextUtils.isEmpty(getTextString())) { + int index = getSelectionIndex(); + if (index >= 0 && index < mSections.size()) { + mSections.remove(getSelectionIndex()); + } + }else { + isMultiDelete = true; + int selectionStart = getSelectionStart(); + int selectionEnd = getSelectionEnd(); + final int indexStart = getSelectionIndexFromStart(selectionStart);//光标起始段落 + if (indexStart >= 0 && indexStart < mSections.size()) { + final TextSection section = mSections.get(indexStart); + if (isMultiSelection || selectionStart < selectionEnd) {//如果是多选,注意头和尾之间的style段落都会被清除 + int indexEnd = getSelectionIndexFromStart(selectionEnd);//光标结束位置 + if (indexStart < indexEnd) { + List sections = new ArrayList<>(); + for (int i = indexStart + 1; i <= indexEnd; i++) { + sections.add(mSections.get(i)); + } + mSections.removeAll(sections); + } + getText().delete(selectionStart, selectionEnd); + setSectionStyle(section, indexStart); + return true; + } + } + } + break; + case KeyEvent.KEYCODE_ENTER: + int index = getSelectionIndex(); + isEnter = true; + if (index >= 0 && index < mSections.size()) { + TextSection section = mSections.get(index); + mSections.add(index, section.cloneTextSelection()); + } + break; + } + return super.onKeyDown(keyCode, event); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mParent == null) { + mParent = (RichLinearLayout) getParent(); + } + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_DEL: + break; + case KeyEvent.KEYCODE_ENTER: + return false; + } + return false; + } + + private String getTextString() { + return getText().toString(); + } + + /** + * 获取所处的段落 + * + * @param index index 所处下标位置 + * @return 段落位置 + */ + private int getSectionIndex(int index) { + String text = getTextString(); + if (TextUtils.isEmpty(text)) + return 0; + String[] selections = text.split("\n", -1); + int length = selections[0].length(); + if (text.length() <= index) { + return selections.length - 1; + } + if (selections.length == 1 || length >= index) { + return 0; + } + return findIndex(text.substring(0, index)); + } + + /** + * @return 返回光标所在段落的位置 + */ + int getSelectionIndex() { + String text = getTextString(); + if (TextUtils.isEmpty(text)) + return 0; + int start = getSelectionStart();//光标位置 + String[] selections = text.split("\n", -1); + int length = selections[0].length(); + //如果文本长度==光标位置 则是最后一个段落 + if (text.length() <= start) { + return selections.length - 1; + } + //如果没有会车符号,或者光标位置在第一个个段落 + if (selections.length == 1 || length >= start) { + return 0; + } else { + return findIndex(text.substring(0, start)); + } + } + + int getSelectionIndexFromStart(int start) { + String text = getTextString(); + if (TextUtils.isEmpty(text)) + return 0; + //Log.e("count", " -- " + findIndex(text.substring(0, start))); + String[] selections = text.split("\n", -1); + int length = selections[0].length(); + //如果文本长度==光标位置 则是最后一个段落 + if (text.length() <= start) { + return selections.length - 1; + } + //如果没有会车符号,或者光标位置在第一个个段落 + if (selections.length == 1 || length >= start) { + return 0; + } else { + return findIndex(text.substring(0, start)); + } + } + + private static Pattern mPattern = Pattern.compile("\n"); + + /** + * 段落下标 + * + * @param srcText 文本 + * @return 段落下标位置 + */ + private static int findIndex(String srcText) { + int count = 0; + Matcher m = mPattern.matcher(srcText); + while (m.find()) { + count++; + } + return count; + } + + /** + * 获取段落文本 + * + * @return 获取段落文本 + */ + private String getStringFromIndex(int index) { + String text = getTextString(); + if (TextUtils.isEmpty(text) || !text.contains("\n")) + return text; + String[] selections = text.split("\n"); + if (index >= selections.length) { + return text; + } + return selections[index]; + } + + private boolean isDeleteSection() { + return getSelectionStart() == getSectionStart(); + } + + /** + * 获取光标所在行数,对应段落 + * + * @return 光标所在行数 0,1,2,3 + */ + private int getCurrentCursorLine() { + int selectionStart = getSelectionStart(); + Layout layout = getLayout(); + if (selectionStart != -1) { + return layout.getLineForOffset(selectionStart); + } + return -1; + } + + /** + * 获取段落的文本起始位置 + * + * @return 起始位置 + */ + private int getSectionStart() { + int index = getSelectionIndex(); + if (index == 0) return 0; + String text = getTextString(); + if (TextUtils.isEmpty(text)) + return 0; + int start = 0; + String[] selections = text.split("\n", -1); + int length = selections[0].length(); + for (int i = 0; i < index; i++) { + start += selections[i].length() + 1; + } + return start; + } + + /** + * 获取段落的文本起始位置 + * + * @return 起始位置 + */ + int getSectionStart(int index) { + if (index == 0) return 0; + String text = getTextString(); + if (TextUtils.isEmpty(text)) + return 0; + int start = 0; + String[] selections = text.split("\n", -1); + if (index >= selections.length) + index = selections.length; + for (int i = 0; i < index; i++) { + start += selections[i].length() + 1; + } + return start; + } + + /** + * 获取段落的文本结束位置 + * + * @return 结束位置 + */ + private int getSectionEnd() { + String text = getTextString(); + int index = getSelectionIndex(); + int end = 0; + String[] selections = text.split("\n", -1); + if (index == 0) return selections[0].length(); + for (int i = 0; i <= index; i++) { + end += selections[i].length() + 1; + if (i == index) { + end -= 1; + } + } + return end; + } + + /** + * 获取段落的文本结束位置 + * + * @return 结束位置 + */ + private int getSectionEnd(int index) { + String text = getTextString(); + int end = 0; + String[] selections = text.split("\n", -1); + if (index == 0) return selections[0].length(); + if (index >= selections.length) { + return text.length(); + } + for (int i = 0; i <= index; i++) { + end += selections[i].length() + 1; + if (i == index) { + end -= 1; + } + } + return end; + } + + void setBold(boolean isBold) { + int index = getSelectionIndex(); + if (index >= 0 && index < mSections.size()) { + mSections.get(index).setBold(isBold); + } + Editable edit = getEditableText(); + int star = getSectionStart(); + int end = getSectionEnd(); + if (isBold) { + edit.setSpan(new StyleSpan(Typeface.BOLD), + star, + end, + Typeface.BOLD); + } else { + StyleSpan[] styleSpans = edit.getSpans(star, + end, StyleSpan.class); + for (CharacterStyle span : styleSpans) { + if (span instanceof StyleSpan && ((StyleSpan) span).getStyle() == Typeface.BOLD) + edit.removeSpan(span); + } + } + } + + + void setItalic(boolean isItalic) { + int index = getSelectionIndex(); + if (index >= 0 && index < mSections.size()) { + mSections.get(index).setItalic(isItalic); + } + Editable edit = getEditableText(); + int star = getSectionStart(); + int end = getSectionEnd(); + if (isItalic) { + edit.setSpan(new StyleSpan(Typeface.ITALIC), + star, + end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + StyleSpan[] styleSpans = edit.getSpans(star, + end, StyleSpan.class); + for (CharacterStyle span : styleSpans) { + if (span instanceof StyleSpan && ((StyleSpan) span).getStyle() == Typeface.ITALIC) + edit.removeSpan(span); + } + } + } + + void setItalic(boolean isItalic, int index) { + if (index >= 0 && index < mSections.size()) { + mSections.get(index).setItalic(isItalic); + } + Editable edit = getEditableText(); + int star = getSectionStart(); + int end = getSectionEnd(); + if (isItalic) { + edit.setSpan(new StyleSpan(Typeface.ITALIC), + star, + end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + StyleSpan[] styleSpans = edit.getSpans(star, + end, StyleSpan.class); + for (CharacterStyle span : styleSpans) { + if (span instanceof StyleSpan && ((StyleSpan) span).getStyle() == Typeface.ITALIC) + edit.removeSpan(span); + } + } + } + + void setBold(boolean isBold, int index) { + if (index >= 0 && index < mSections.size()) { + mSections.get(index).setBold(isBold); + } + Editable edit = getEditableText(); + int star = getSectionStart(index); + int end = getSectionEnd(index); + if (star >= end) + return; + if (isBold) { + edit.setSpan(new StyleSpan(Typeface.BOLD), + star, + end, + Typeface.BOLD); + } else { + StyleSpan[] styleSpans = edit.getSpans(star, + end, StyleSpan.class); + for (CharacterStyle span : styleSpans) { + if (span instanceof StyleSpan && ((StyleSpan) span).getStyle() == Typeface.BOLD) + edit.removeSpan(span); + } + } + } + + /** + * 中横线,即删除线 + * + * @param isMidLine isMidLine + */ + void setMidLine(boolean isMidLine) { + int index = getSelectionIndex(); + if (index >= 0 && index < mSections.size()) { + mSections.get(index).setMidLine(isMidLine); + } + Editable edit = getEditableText(); + int star = getSectionStart(); + int end = getSectionEnd(); + if (isMidLine) { + edit.setSpan(new StrikethroughSpan(), + star, + end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + StrikethroughSpan[] styleSpans = edit.getSpans(star, + end, StrikethroughSpan.class); + for (StrikethroughSpan span : styleSpans) { + edit.removeSpan(span); + } + } + } + + /** + * 中横线,即删除线 + * + * @param isMidLine isMidLine + */ + void setMidLine(boolean isMidLine, int index) { + if (index >= 0 && index < mSections.size()) { + mSections.get(index).setMidLine(isMidLine); + } + Editable edit = getEditableText(); + int star = getSectionStart(); + int end = getSectionEnd(); + if (isMidLine) { + edit.setSpan(new StrikethroughSpan(), + star, + end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + StrikethroughSpan[] styleSpans = edit.getSpans(star, + end, StrikethroughSpan.class); + for (StrikethroughSpan span : styleSpans) { + edit.removeSpan(span); + } + } + } + + + void setAlignStyle(int align) { + int index = getSelectionIndex(); + if (index >= 0 && index < mSections.size()) { + mSections.get(index).setAlignment(align); + } + Editable edit = getEditableText(); + int star = getSectionStart(); + int end = getSectionEnd(); + AlignmentSpan[] spans = edit.getSpans(star, end, AlignmentSpan.class); + for (AlignmentSpan span : spans) { + edit.removeSpan(span); + } + edit.setSpan(getAlignmentSpan(align), star, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + void setAlignStyle(int align, int index) { + if (index >= 0 && index < mSections.size()) { + mSections.get(index).setAlignment(align); + } + Editable edit = getEditableText(); + int star = getSectionStart(index); + int end = getSectionEnd(index); + if (star >= end) + return; + edit.setSpan(getAlignmentSpan(align), star, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + void setColorSpan(String color) { + int index = getSelectionIndex(); + if (index >= 0 && index < mSections.size()) { + mSections.get(index).setColorHex(color); + } + setColorSpan(Color.parseColor("#" + color)); + } + + void setColorSpan(int color) { + Editable edit = getEditableText(); + int star = getSectionStart(); + int end = getSectionEnd(); + ForegroundColorSpan[] styleSpans = edit.getSpans(star, end, ForegroundColorSpan.class); + for (ForegroundColorSpan span : styleSpans) { + edit.removeSpan(span); + } + edit.setSpan(new ForegroundColorSpan(color), star, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + void setColorSpan(int color, int index) { + Editable edit = getEditableText(); + int star = getSectionStart(index); + int end = getSectionEnd(index); + if (star >= end) + return; + ForegroundColorSpan[] styleSpans = edit.getSpans(star, end, ForegroundColorSpan.class); + for (ForegroundColorSpan span : styleSpans) { + edit.removeSpan(span); + } + edit.setSpan(new ForegroundColorSpan(color), star, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + private void setTextSizeSpan(int index, boolean isIncrease) { + Editable edit = getEditableText(); + int textSize = 16; + if (index >= 0 && index < mSections.size()) { + TextSection section = mSections.get(index); + int s = section.getTextSize(); + s = s + (isIncrease ? 1 : -1); + section.setTextSize(s); + textSize = s; + } + setTextSizeSpan(index, textSize); + } + + + void setTextSizeSpanIncrease(boolean isIncrease) { + int index = getSelectionIndex(); + Editable edit = getEditableText(); + int textSize = 16; + if (index >= 0 && index < mSections.size()) { + TextSection section = mSections.get(index); + int s = section.getTextSize(); + if ((s == 70 && isIncrease) || (s == 10 && !isIncrease))// 10- 70 sp + return; + s = s + (isIncrease ? 1 : -1); + section.setTextSize(s); + if (mListener != null) { + mListener.onSectionChange(section); + } + textSize = s; + } + setTextSizeSpan(textSize); + } + + + private void setTextSizeSpan(int textSize, int index) { + Editable edit = getEditableText(); + int star = getSectionStart(index); + int end = getSectionEnd(index); + if (star >= end) + return; + AbsoluteSizeSpan[] styleSpans = edit.getSpans(star, end, AbsoluteSizeSpan.class); + for (AbsoluteSizeSpan span : styleSpans) { + edit.removeSpan(span); + } + edit.setSpan(new AbsoluteSizeSpan(UI.dipToPx(getContext(), textSize)), star, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + private void setTextSizeSpan(int textSize) { + Editable edit = getEditableText(); + int star = getSectionStart(); + int end = getSectionEnd(); + if (star >= end) + return; + AbsoluteSizeSpan[] styleSpans = edit.getSpans(star, end, AbsoluteSizeSpan.class); + for (AbsoluteSizeSpan span : styleSpans) { + edit.removeSpan(span); + } + edit.setSpan(new AbsoluteSizeSpan(UI.dipToPx(getContext(), textSize)), star, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + + /** + * 设置字体大小 + * @param textSize textSize + */ + void setTextSize(int textSize) { + Editable edit = getEditableText(); + int index = getSelectionIndex(); + int star = getSectionStart(); + int end = getSectionEnd(); + if (star >= end) + return; + if (index >= 0 && index < mSections.size()) { + TextSection section = mSections.get(index); + section.setTextSize(textSize); + if (mListener != null) { + mListener.onSectionChange(section); + } + } + AbsoluteSizeSpan[] styleSpans = edit.getSpans(star, end, AbsoluteSizeSpan.class); + for (AbsoluteSizeSpan span : styleSpans) { + edit.removeSpan(span); + } + edit.setSpan(new AbsoluteSizeSpan(UI.dipToPx(getContext(), textSize)), star, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + update(index); + } + + + /** + * 字体大小改变,会导致后续的段落无法及时刷新,调用这个方法即可 + * + * @param position 段落位置 + */ + private void update(int position) { + if (mSections == null || mSections.size() <= 1 || position >= mSections.size() - 1) + return; + for (int i = position; i < mSections.size(); i++) { + TextSection section = mSections.get(i); + setSectionStyle(section, i); + } + } + + /** + * 合并RichEditText + * + * @param removeEdit 被移除的 + * @param mergeEditText 合并后的 + */ + static void mergeRichEditText(RichEditText removeEdit, RichEditText mergeEditText, String content) { + if (TextUtils.isEmpty(removeEdit.getTextString())) { + mergeEditText.mSections = removeEdit.mSections; + } else { + List sections = removeEdit.mSections; + if (TextUtils.isEmpty(content)) { + mergeEditText.mSections = sections; + } else { + mergeEditText.mSections.remove(0); + List mergeSections = new ArrayList<>(); + for (int i = 0; i < sections.size(); i++) { + mergeSections.add(sections.get(i).cloneTextSelection()); + } + mergeSections.addAll(mergeEditText.mSections); + mergeEditText.mSections = mergeSections; + } + } + for (int i = 0; i < mergeEditText.mSections.size(); i++) { + TextSection section = mergeEditText.mSections.get(i); + mergeEditText.setSectionStyle(section, i); + } + } + + /** + * 合并RichEditText + * + * @param removeEdit 被移除的 需要移除第一个段落,合并到最后一个段落,如果是空的就没必要合并 + * @param mergeEditText 合并后的 + * @param mergeIndex 合并的位置,末尾处 + */ + static void mergeRichEditText(RichEditText removeEdit, RichEditText mergeEditText, int mergeIndex) { + List sections = removeEdit.mSections; + sections.remove(0);//移除第一个段落 + for (TextSection section : sections) { + mergeEditText.mSections.add(section.cloneTextSelection()); + } + for (int i = 0; i < mergeEditText.mSections.size(); i++) { + TextSection section = mergeEditText.mSections.get(i); + mergeEditText.setSectionStyle(section, i); + } + } + + /** + * 继承风格,适用与RichEditText 起点、中间、结尾 插入图片的情况 + * 起点:oldEditText newEditText继承oldEditText全部段落,oldEditText被移除 + * 中间:oldEditText 保留下标之前、包括下标所在段落 , newEditText 继承oldEditText下标之后、包括下标所在段落 + * 结尾:oldEditText 保留所有段落 , newEditText继承oldEditText最后一个段落 + * + * @param oldEditText 被继承的 RichEditText + * @param newEditText 继承的 RichEditText + * @param indexType 下标所在位置类型 + * static final int INDEX_START = 0; 起点 + * static final int INDEX_MID = 1; 居中 + * static final int INDEX_END = 2; 末尾 + */ + static void inheritStyle(RichEditText oldEditText, RichEditText newEditText, int index, int indexType) { + switch (indexType) { + case INDEX_START: + newEditText.mSections = oldEditText.mSections; + break; + case INDEX_MID: + newEditText.mSections.clear(); + List oldSections = oldEditText.mSections; + if (oldSections.size() == 1) { + newEditText.mSections.add(oldSections.get(0).cloneTextSelection()); + } else { + List removes = new ArrayList<>(); + for (int i = index; i < oldSections.size(); i++) { + TextSection section = oldSections.get(i); + newEditText.mSections.add(section.cloneTextSelection()); + if (i != index) { + removes.add(section); + } + } + oldSections.removeAll(removes); + for (int i = 0; i < oldSections.size(); i++) { + TextSection section = oldSections.get(i); + //重新设置被分割的RichEditText样式,因为它重新设置了Text + oldEditText.setSectionStyle(section, i); + } + } + break; + case INDEX_END: + newEditText.mSections.clear(); + newEditText.mSections.add(oldEditText.mSections.get(oldEditText.mSections.size() - 1).cloneTextSelection()); + break; + } + for (int i = 0; i < newEditText.mSections.size(); i++) { + TextSection section = newEditText.mSections.get(i); + newEditText.setSectionStyle(section, i); + } + } + + + /** + * 切换段落 + */ + public interface OnSectionChangeListener { + void onSectionChange(TextSection section); + } + + private static AlignmentSpan getAlignmentSpan(int align) { + return new AlignmentSpan.Standard(getAlignment(align)); + } + + private static AlignmentSpan getAlignmentSpan(TextSection section) { + return new AlignmentSpan.Standard(getAlignment(section.getAlignment())); + } + + private static Layout.Alignment getAlignment(int alignment) { + if (alignment == TextSection.CENTER) { + return Layout.Alignment.ALIGN_CENTER; + } else if (alignment == TextSection.RIGHT) + return Layout.Alignment.ALIGN_OPPOSITE; + return Layout.Alignment.ALIGN_NORMAL; + } + + /** + * 生成段落文本输出 + * 段落如果是空白的,也就是只有\n 连空格都没有, 则在上一个段落 + \n + * + * @param preViewIsImage 图片的下一个段落要在起始 + \n 回车 + * @param isLastIndex 是否是最后一个 + * @return 段落列表 + */ + @SuppressWarnings("all") + List getTextSections() { + String text = getTextString(); + String[] selections = text.split("\n", -1); + int length = selections.length; + if (length == 1 && TextUtils.isEmpty(text.trim())) + return null; + + List list = new ArrayList<>(); + for (int i = 0; i < length; i++) { + TextSection section = mSections.get(i).cloneTextSelection(); + section.setText(selections[i]); + list.add(section); + } + return list; + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/widget/rich/RichLinearLayout.java b/app/src/main/java/net/oschina/app/improve/widget/rich/RichLinearLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..0bd46971a4278ee14c9979595225c2ab44e70e9f --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/rich/RichLinearLayout.java @@ -0,0 +1,578 @@ +package net.oschina.app.improve.widget.rich; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.inputmethodservice.Keyboard; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatEditText; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.LinearLayout; + +import net.oschina.app.R; + +import java.util.List; + +/** + * 真正的编辑器内容布局 + * Created by huanghaibin on 2017/8/3. + */ +@SuppressWarnings("unused") +public class RichLinearLayout extends LinearLayout { + RichScrollView mParent; + RichEditText mFocusView; + ImagePanel mFocusPanel; + RichEditText.OnSectionChangeListener mListener; + AppCompatEditText mEditTitle, mEditSummary; + + + public RichLinearLayout(Context context) { + this(context, null); + } + + public RichLinearLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + setOrientation(VERTICAL); + init(context); + } + + @SuppressWarnings("all") + private void init(Context context) { + LayoutInflater.from(context).inflate(R.layout.rich_linear_layout, this, true); + mEditTitle = (AppCompatEditText) findViewById(R.id.et_title); + + mEditTitle.setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mParent.mParent.mContentPanel.getVisibility() == VISIBLE) { + mParent.mParent.setAdjustNothing(); + } + postDelayed(new Runnable() { + @Override + public void run() { + mParent.mParent.mContentPanel.setVisibility(GONE); + mParent.mParent.setAdjustResize(); + } + }, 500); + return false; + } + }); + mEditTitle.setOnFocusChangeListener(new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (mParent == null || mParent.mParent == null) + return; + mParent.mParent.mRichBar.setBarEnable(!hasFocus); + } + }); + +// mEditSummary.setOnTouchListener(new OnTouchListener() { +// @Override +// public boolean onTouch(View v, MotionEvent event) { +// if (mParent.mParent.mContentPanel.getVisibility() == VISIBLE) { +// mParent.mParent.setAdjustNothing(); +// } +// postDelayed(new Runnable() { +// @Override +// public void run() { +// mParent.mParent.mContentPanel.setVisibility(GONE); +// mParent.mParent.setAdjustResize(); +// } +// }, 500); +// return false; +// } +// }); + + RichEditText editText = new RichEditText(context, mListener); + mFocusView = editText; + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + editText.setLayoutParams(params); + setPadding(UI.dipToPx(context, 16), 0, UI.dipToPx(context, 16), 0); + addView(editText); + + } + + + String getTitle() { + return mEditTitle.getText().toString().trim().replace("\n",""); + } + + String getSummary() { + return mEditSummary.getText().toString().trim(); + } + + /** + * 删除文本判断第一个字符的操作 + * + * @param editText 当前富文本editText + */ + @SuppressLint("SetTextI18n") + void delete(RichEditText editText) { + int count = getChildCount(); + if (count <= 2) + return; + for (int i = 1; i < count; i++) { + View view = getChildAt(i); + if (view == editText) { + String content = editText.getText().toString(); + if (i == 0) {//如果是第一个 RichEditText 而且没有输入,则删除 + if (!TextUtils.isEmpty(content)) + return; + removeView(editText); + View nextView = getChildAt(0); + if (nextView instanceof ImagePanel) { + mFocusPanel = (ImagePanel) nextView; + mFocusPanel.setFocusMode(); + } else { + mFocusView = (RichEditText) nextView; + mFocusView.requestFocus(); + } + return; + } + View preView = getChildAt(i - 1); + if (preView instanceof ImagePanel) { + ImagePanel panel = (ImagePanel) preView; + if (panel.isDeleteMode) {//有焦点,直接remove ImagePanel , + int index = i - 2; + if (index >= 0 && index < getChildCount() && getChildAt(index) instanceof RichEditText) {//合并 RichEditText + RichEditText preEdit = (RichEditText) getChildAt(index);//被移除的 RichEditText + String text = preEdit.getText().toString() + content; + editText.setText(text); + mFocusView = editText; + mFocusView.setSelection(mFocusView.getText().toString().length()); + RichEditText.mergeRichEditText(preEdit, mFocusView, content); + removeView(preEdit); + removeView(panel); + } else { + removeView(panel); + } + } else { + if (TextUtils.isEmpty(content) && i != count - 1) { + removeView(editText); + panel.showDeleteMode(i); + mFocusPanel = panel; + mFocusPanel.setFocusMode(); + } else { + //没有焦点请求显示焦点编辑模式 + panel.showDeleteMode(i); + mFocusPanel = panel; + } + } + } else { + RichEditText preEdit = (RichEditText) preView; + preEdit.setText(preEdit.getText().toString() + "\n" + content); + preEdit.setSelection(preEdit.getText().toString().length()); + mFocusView = preEdit; + removeView(editText); + } + } + } + } + + /** + * 插入图片 width=match_parent, height=wrap_content + * + * @param image 图片路径 + */ + void insertImagePanel(String image) { + ImagePanel panel = new ImagePanel(getContext()); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMargins(0, UI.dipToPx(getContext(), 8), 0, UI.dipToPx(getContext(), 8)); + panel.setLayoutParams(params); + panel.setImagePath(image); + int count = getChildCount(); + for (int i = 0; i < count; i++) { + View view = getChildAt(i); + if (view instanceof RichEditText) { + if (view.isFocused() || view == mFocusView) { + RichEditText curEditText = (RichEditText) view;//当前正在操作的 RichEditText + int sectionIndex = curEditText.getSelectionIndex(); + int indexType = 0; + String content = curEditText.getText().toString(); + if (TextUtils.isEmpty(content)) {//如果插入行没有文本,直接移除 + removeView(view); + addView(panel, i); + } else { + int start = curEditText.getSelectionStart();//光标起点 + if (start == 0) {//在RichEditText起点插入,忽略 + addView(panel, i); + return; + } else if (start != content.length()) {//在EditText中间插入,分割,注意保留style + indexType = RichEditText.INDEX_MID;//合并的时机 + curEditText.setText(content.substring(0, start)); + addView(panel, i + 1); + final RichEditText editText = new RichEditText(getContext(), mListener); + editText.setText(content.substring(start)); + mFocusView = editText; + RichEditText.inheritStyle(curEditText, editText, sectionIndex, indexType);//合并的时机 + addView(editText, i + 2); + if (mParent == null) { + mParent = (RichScrollView) getParent(); + } + postDelayed(new Runnable() { + @Override + public void run() { + if (mParent == null) + return; + mParent.smoothScrollTo(0, editText.getTop()); + editText.requestFocus(); + //openKeyboard(editText); + } + }, 500); + return; + } else {// 在RichEditText末尾插入,忽略 + indexType = RichEditText.INDEX_END; + addView(panel, i + 1); + } + } + if (i == count - 1) {//加在最后一个,自动插入输入控件 + final RichEditText editText = new RichEditText(getContext(), mListener); + mFocusView = editText; + addView(editText); + RichEditText.inheritStyle(curEditText, editText, sectionIndex, indexType); + if (mParent == null) { + mParent = (RichScrollView) getParent(); + } + postDelayed(new Runnable() { + @Override + public void run() { + if (mParent == null) + return; + mParent.smoothScrollTo(0, editText.getTop()); + editText.requestFocus(); + //openKeyboard(editText); + } + }, 500); + } + } + } + } + } + + /** + * * 调整布局 + * 触发情况 + * EditText + ImagePanel + EditText ImagePanel被删除的情况,EdiText要进行合并 + * ImagePanel 下已经有EditText 点击回车的情况 + * + * @param imagePanel 图片布局 + * @param isDelete 是否是删除操作,否则是回车 + */ + void adjustLayout(final ImagePanel imagePanel, boolean isDelete) { + int count = getChildCount(); + for (int i = 0; i < count; i++) { + View view = getChildAt(i); + if (view == imagePanel) { + if (isDelete) { + adjustLayoutDeleteImage(imagePanel, count, i); + } else { + adjustLayoutEnter(imagePanel, count, i); + } + } + } + } + + /** + * 调整图片回车后的布局, + * ImagePanel + RichEditText 的情况 RichEditText获得焦点移动光标 + * ImagePanel + ImagePanel 的情况插入 RichEditText获得焦点 + * + * @param imagePanel 被删除的图片 + * @param count 子View数量 + * @param index 被删除图片的位置 + */ + private void adjustLayoutEnter(final ImagePanel imagePanel, int count, int index) { + int nextIndex = index + 1; + if (nextIndex >= 0 && nextIndex < count) { + final View nextView = getChildAt(nextIndex); + if (nextView instanceof ImagePanel) {//如果下个View是 ImagePanel ,则插入RichEditText,往上遍历RichEditText + final RichEditText editText = new RichEditText(getContext(), mListener); + mFocusView = editText; + for (int i = index - 1; i >= 0; i--) { + View view = getChildAt(i); + if (view instanceof RichEditText) { + editText.mSections.clear(); + RichEditText preEditText = (RichEditText) view; + editText.mSections.add(preEditText.mSections.get(preEditText.mSections.size() - 1).cloneTextSelection()); + break; + } + } + addView(editText, index + 1); + if (mParent == null) { + mParent = (RichScrollView) getParent(); + } + postDelayed(new Runnable() { + @Override + public void run() { + if (mParent == null) + return; + mParent.smoothScrollTo(0, editText.getTop()); + editText.requestFocus(); + openKeyboard(editText); + } + }, 200); + } else { + RichEditText editText = (RichEditText) nextView; + editText.setSelection(editText.getText().toString().length()); + mFocusView = editText; + mParent.smoothScrollTo(0, editText.getTop()); + postDelayed(new Runnable() { + @Override + public void run() { + mFocusView.requestFocus(); + } + }, 100); + } + } + } + + /** + * 调整删除图片后的布局,主要是 RichEditText + ImagePanel + RichEditText 的情况合并两个RichEditText + * + * @param imagePanel 被删除的图片 + * @param count 子View数量 + * @param index 被删除图片的位置 + */ + @SuppressLint("SetTextI18n") + private void adjustLayoutDeleteImage(final ImagePanel imagePanel, int count, int index) { + if (index == 0) { + removeView(imagePanel); + setRichEditTextFocus(); + return; + } + int nextIndex = index + 1; + int preIndex = index - 1; + View preView; + if (preIndex >= 0 && preIndex < count) { + preView = getChildAt(preIndex); + if (preView instanceof ImagePanel) {//如果前一个是 ImagePanel ,直接移除当前ImagePanel + setRichEditTextFocus(); + removeView(imagePanel); + return; + } + } else { + setRichEditTextFocus(); + removeView(imagePanel); + return; + } + if (nextIndex >= 0 && nextIndex < count) { + final View nextView = getChildAt(nextIndex); + if (nextView instanceof ImagePanel) {//下一个是ImagePanel + ImagePanel nextPanel = (ImagePanel) nextView; + removeView(imagePanel); + mFocusPanel = nextPanel; + postDelayed(new Runnable() { + @Override + public void run() { + mFocusPanel.setFocusMode(); + } + }, 300); + return; + }// + RichEditText preEditText = (RichEditText) preView;//前一个是 RichEditText + RichEditText nextEditText = (RichEditText) nextView;// 下一个也是 RichEditText + String content = nextEditText.getText().toString(); + if (!TextUtils.isEmpty(content)) { + int preSectionIndex = preEditText.mSections.size() - 1; + preEditText.setText(preEditText.getText().toString() + content); + RichEditText.mergeRichEditText(nextEditText, preEditText, preSectionIndex);//合并 段落 + } + preEditText.setSelection(preEditText.getText().toString().length());//合并两个 RichEditText + removeView(nextEditText); + mFocusView = preEditText; + removeView(imagePanel); + setRichEditTextFocus(); + } + } + + /** + * 让当前RichEditText获取焦点 + */ + private void setRichEditTextFocus() { + mFocusView.setFocusable(true); + mFocusView.setFocusableInTouchMode(true); + mFocusView.requestFocus(); + } + + void setBold(boolean isBold) { + mFocusView.setBold(isBold); + } + + + void setItalic(boolean isItalic) { + mFocusView.setItalic(isItalic); + } + + void setMidLine(boolean isMidLine) { + mFocusView.setMidLine(isMidLine); + } + + void setAlignStyle(int align) { + mFocusView.setAlignStyle(align); + } + + void setColorSpan(String color) { + mFocusView.setColorSpan(color); + } + + void setTextSizeSpan(boolean isIncrease) { + mFocusView.setTextSizeSpanIncrease(isIncrease); + } + + void setTextSize(int textSize) { + mFocusView.setTextSize(textSize); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mParent == null) { + mParent = (RichScrollView) getParent(); + } + } + + private void openKeyboard(EditText view) { + view.setFocusable(true); + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) + imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); + } + + /** + * 初始化界面 + * 遍历段落,连续的TextSection只能有一个richEditText + * 判断下一个段落是不是图片,是图片就插入图片,同时新建一个RichEditText + *

    + * 注意初始化日记的时候导入段落 + * 最终使用的时候要在每一个文本段落起始位置和末尾都去掉一个 \n + * 然后前面有多少个 \n 就加多少个段落,继承当前段落 + * 后面有多少个 \n 就追加多少个段落,继承当前段落 + */ + @SuppressLint("SetTextI18n") + void init(List

    sections) { + if (sections == null || sections.size() == 0) + return; + mFocusView = null; + mFocusPanel = null; + removeAllViews(); + RichEditText richEditText = null; + for (int i = 0; i < sections.size(); i++) { + Section section = sections.get(i); + boolean nextIsImage = false; + int nextIndex = i + 1; + Section nextSection = null; + if (nextIndex < sections.size()) { + nextSection = sections.get(nextIndex); + nextIsImage = nextSection instanceof ImageSection; + } + if (section instanceof ImageSection) { + ImageSection imageSection = (ImageSection) section; + ImagePanel panel = new ImagePanel(getContext()); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMargins(0, UI.dipToPx(getContext(), 8), 0, UI.dipToPx(getContext(), 8)); + panel.setLayoutParams(params); + panel.setImagePath(imageSection.getFilePath()); + addView(panel); + richEditText = null; + } else { + TextSection textSection = (TextSection) section; + if (richEditText == null) {//初始化的时候必须要移除所有默认段落样式 + richEditText = new RichEditText(getContext(), mListener); + richEditText.mSections.clear(); + } + String text = textSection.getText(); + //最终使用的时候要在除了第一个是RichEditText每一个文本段落起始位置和末尾都去掉一个 \n +// if (text.startsWith("\n") && i != 0) { +// text = text.substring(1, text.length()); +// } +// if (text.endsWith("\n")) { +// text = text.substring(0, text.length() - 1); +// } + String copyText = text; + //一个段落前面有多少个\n,则前面就有多少行,即多少个空的段落 + while (copyText.startsWith("\n")) { + copyText = copyText.substring(1, copyText.length()); + TextSection preSection = textSection.cloneTextSelection(); + preSection.setText(""); + richEditText.mSections.add(preSection); + } + //中间其实就是直接把文本设置进去即可 + String line = (TextUtils.isEmpty(richEditText.getText().toString()) || + i == 0) ? "" : "\n"; + richEditText.setText(richEditText.getText().toString() + line + text); + TextSection midSection = textSection.cloneTextSelection(); + midSection.setText(""); + richEditText.mSections.add(midSection); + //一个段落结尾有多少个\n,则后面就有多少行,即多少个空的段落 + while (copyText.endsWith("\n")) { + copyText = copyText.substring(0, copyText.length() - 1); + TextSection nextTextSection = textSection.cloneTextSelection(); + nextTextSection.setText(""); + richEditText.mSections.add(nextTextSection.cloneTextSelection()); + } + if (richEditText.getParent() == null) { + addView(richEditText); + } + if (nextIsImage || nextSection == null) {//如果下一个段落是图片,则格式化样式 + for (int j = 0; j < richEditText.mSections.size(); j++) { + TextSection style = richEditText.mSections.get(j); + richEditText.setSectionStyle(style, j); + } + } + } + } + int count = getChildCount(); + View view = getChildAt(count - 1); + if (view instanceof ImagePanel) { + final RichEditText editText = new RichEditText(getContext(), mListener); + addView(editText); + mFocusView = editText; + post(new Runnable() { + @Override + public void run() { + editText.requestFocus(); + } + }); + } else { + final RichEditText editText = (RichEditText) view; + editText.setSelection(editText.getText().length()); + mFocusView = editText; + post(new Runnable() { + @Override + public void run() { + editText.requestFocus(); + } + }); + } + } + + /** + * 返回日记数量 + * + * @return 返回日记数量 + */ + int getImageCount() { + int count = getChildCount(); + int imageCount = 0; + for (int i = 0; i < count; i++) { + if (getChildAt(i) instanceof ImagePanel) + ++imageCount; + } + return imageCount; + } + + + /** + * fan + */ + List createSectionList() { + return mFocusView.getTextSections(); + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/rich/RichScrollView.java b/app/src/main/java/net/oschina/app/improve/widget/rich/RichScrollView.java new file mode 100644 index 0000000000000000000000000000000000000000..9b2ac9cc2acbe775e33c9f687379b33ffa757af1 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/rich/RichScrollView.java @@ -0,0 +1,68 @@ +package net.oschina.app.improve.widget.rich; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.v4.widget.NestedScrollView; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import java.util.List; + +/** + * 编辑滚动布局 + * Created by huanghaibin on 2017/8/3. + */ + +public class RichScrollView extends NestedScrollView { + RichLinearLayout mRichLinearLayout; + RichEditLayout mParent; + + public RichScrollView(Context context) { + this(context, null); + } + + public RichScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + setVerticalScrollBarEnabled(true); + setHorizontalScrollBarEnabled(false); + mRichLinearLayout = new RichLinearLayout(context); + addView(mRichLinearLayout); + setOnTouchListener(new OnTouchListener() { + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouch(View v, MotionEvent event) { + mRichLinearLayout.mFocusView.setFocusable(true); + mRichLinearLayout.mFocusView.setFocusableInTouchMode(true); + mRichLinearLayout.mFocusView.requestFocus(); + mRichLinearLayout.mFocusView.onTouchEvent(event); + return true; + } + }); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mParent == null) { + mParent = (RichEditLayout) getParent(); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + } + + List createSectionList() { + return mRichLinearLayout.createSectionList(); + } + + void addImagePanel(String image) { + mRichLinearLayout.insertImagePanel(image); + } + + boolean isKeyboardOpen() { + return mParent.isKeyboardOpen(); + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/widget/rich/Section.java b/app/src/main/java/net/oschina/app/improve/widget/rich/Section.java new file mode 100644 index 0000000000000000000000000000000000000000..3f1fd58be4448893394a8eb9fea40fe3b2de600b --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/rich/Section.java @@ -0,0 +1,33 @@ +package net.oschina.app.improve.widget.rich; + +import java.io.Serializable; + +/** + * 段落 + * Created by huanghaibin on 2017/8/3. + */ + +public class Section implements Serializable { + private static final long serialVersionUID = -7779844465111313231L; + public static final int TYPE_TEXT = 0; + public static final int TYPE_IMAGE = 1; + /** + * P类型,0:文本 1:图片 + */ + + protected int type; + + protected int index; + public int getIndex() { + return index; + } + public void setIndex(int index) { + this.index = index; + } + public int getType() { + return type; + } + public void setType(int type) { + this.type = type; + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/widget/rich/TextSection.java b/app/src/main/java/net/oschina/app/improve/widget/rich/TextSection.java new file mode 100644 index 0000000000000000000000000000000000000000..5aa61895c191030aa519066b8e6fa72df39f50d0 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/rich/TextSection.java @@ -0,0 +1,109 @@ +package net.oschina.app.improve.widget.rich; + +/** + * 文本段落 + * Created by huanghaibin on 2017/8/3. + */ + +@SuppressWarnings("all") +public class TextSection extends Section implements Cloneable { + private static final long serialVersionUID = -94489191465446041L; + public static final int LEFT = 0; + public static final int CENTER = 1; + public static final int RIGHT = 2; + + + private String text; + + private int textSize = 14; + + private boolean isBold; + + private boolean isMidLine; + + private boolean isItalic; + + private int alignment; + + private String colorHex; + + public TextSection() { + type = TYPE_TEXT; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public int getTextSize() { + return textSize; + } + + public void setTextSize(int textSize) { + this.textSize = textSize; + } + + + public int getAlignment() { + return alignment; + } + + public void setAlignment(int alignment) { + this.alignment = alignment; + } + + public String getColorHex() { + return colorHex; + } + + public void setColorHex(String colorHex) { + this.colorHex = colorHex; + } + + public boolean isBold() { + return isBold; + } + + public void setBold(boolean bold) { + isBold = bold; + } + + public boolean isMidLine() { + return isMidLine; + } + + public void setMidLine(boolean midLine) { + isMidLine = midLine; + } + + public boolean isItalic() { + return isItalic; + } + + public void setItalic(boolean italic) { + isItalic = italic; + } + + public boolean isHeader() { + return textSize != 18; + } + + + @Override + protected Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + public TextSection cloneTextSelection() { + try { + return (TextSection) clone(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/improve/widget/rich/UI.java b/app/src/main/java/net/oschina/app/improve/widget/rich/UI.java new file mode 100644 index 0000000000000000000000000000000000000000..83702958c18a64caf36877a21af7e3705955fdb7 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/rich/UI.java @@ -0,0 +1,70 @@ +package net.oschina.app.improve.widget.rich; + +import android.content.Context; +import android.view.Display; +import android.view.WindowManager; + +/** + * 辅助类 + * Created by huanghaibin on 2017/8/3. + */ +@SuppressWarnings("unused") +final class UI { + + /** + * 获得屏幕的宽度 + * + * @param context context + * @return width + */ + static int getScreenWidth(Context context) { + WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + assert manager != null; + Display display = manager.getDefaultDisplay(); + return display.getWidth(); + } + /** + * 获得屏幕的高度 + * + * @param context context + * @return height + */ + static int getScreenHeight(Context context) { + WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + assert manager != null; + Display display = manager.getDefaultDisplay(); + return display.getHeight(); + } + + /** + * dp转px + * + * @param context context + * @param dpValue dp + * @return px + */ + static int dipToPx(Context context, float dpValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + /** + * px转dp + * + * @param context context + * @param pxValue px + * @return dp + */ + static float pxToDip(Context context, float pxValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return pxValue / scale + 0.5f; + } + + static float getDensityDpi(Context context) { + return context.getResources().getDisplayMetrics().densityDpi; + } + + static float getDensity(Context context) { + return context.getResources().getDisplayMetrics().density; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/widget/togglebutton/ToggleButton.java b/app/src/main/java/net/oschina/app/improve/widget/togglebutton/ToggleButton.java new file mode 100644 index 0000000000000000000000000000000000000000..c09591b04e441bd0c9256989ce824b52b04c06da --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/widget/togglebutton/ToggleButton.java @@ -0,0 +1,337 @@ +package net.oschina.app.improve.widget.togglebutton; + +import android.animation.ObjectAnimator; +import android.animation.TypeEvaluator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Cap; +import android.graphics.Paint.Style; +import android.graphics.RectF; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Property; +import android.view.View; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; + +import net.oschina.app.R; + +/** + * @author Qiujuer + */ +public class ToggleButton extends View { + /** */ + private float radius; + /** + * 开启颜色 + */ + private int onColor = Color.parseColor("#24cf5f"); + /** + * 关闭颜色 + */ + private int offBorderColor = Color.parseColor("#dadbda"); + /** + * 灰色带颜色 + */ + private int offColor = Color.parseColor("#ffffff"); + /** + * 手柄颜色 + */ + private int spotColor = Color.parseColor("#ffffff"); + /** + * 边框颜色 + */ + private int borderColor = offBorderColor; + /** + * 画笔 + */ + private Paint paint; + /** + * 开关状态 + */ + private boolean toggleOn = false; + /** + * 边框大小 + */ + private int borderWidth = 2; + /** + * 垂直中心 + */ + private float centerY; + /** + * 按钮的开始和结束位置 + */ + private float startX, endX; + /** + * 手柄X位置的最小和最大值 + */ + private float spotMinX, spotMaxX; + /** + * 手柄大小 + */ + private int spotSize; + /** + * 手柄X位置 + */ + private float spotX; + /** + * 关闭时内部灰色带高度 + */ + private float offLineWidth; + /** */ + private RectF rect = new RectF(); + + private OnToggleChanged listener; + + private ToggleButton(Context context) { + super(context); + } + + public ToggleButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setup(attrs); + } + + public ToggleButton(Context context, AttributeSet attrs) { + super(context, attrs); + setup(attrs); + } + + public void setup(AttributeSet attrs) { + paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); + paint.setStyle(Style.FILL); + paint.setStrokeCap(Cap.ROUND); + + this.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View arg0) { + toggle(); + } + }); + + TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ToggleButton); + offBorderColor = typedArray.getColor(R.styleable.ToggleButton_offBorderColor, offBorderColor); + onColor = typedArray.getColor(R.styleable.ToggleButton_onColor, onColor); + spotColor = typedArray.getColor(R.styleable.ToggleButton_spotColor, spotColor); + offColor = typedArray.getColor(R.styleable.ToggleButton_offColor, offColor); + borderWidth = typedArray.getDimensionPixelSize(R.styleable.ToggleButton_toggle_border_width, borderWidth); + typedArray.recycle(); + } + + public void toggle() { + toggleOn = !toggleOn; + animateCheckedState(toggleOn); + if (listener != null) { + listener.onToggle(toggleOn); + } + } + + public void toggleOn() { + setToggleOn(); + if (listener != null) { + listener.onToggle(toggleOn); + } + } + + public void toggleOff() { + setToggleOff(); + if (listener != null) { + listener.onToggle(toggleOn); + } + } + + /** + * 设置显示成打开样式,不会触发toggle事件 + */ + public void setToggleOn() { + toggleOn = true; + setAnimatorProperty(true); + } + + /** + * 设置显示成关闭样式,不会触发toggle事件 + */ + public void setToggleOff() { + toggleOn = false; + setAnimatorProperty(false); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, + int bottom) { + super.onLayout(changed, left, top, right, bottom); + + final int width = getWidth(); + final int height = getHeight(); + radius = Math.min(width, height) * 0.5f; + centerY = radius; + startX = radius; + endX = width - radius; + spotMinX = startX + borderWidth; + spotMaxX = endX - borderWidth; + spotSize = height - 4 * borderWidth; + + // update values + setAnimatorProperty(toggleOn); + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + rect.set(0, 0, getWidth(), getHeight()); + paint.setColor(borderColor); + canvas.drawRoundRect(rect, radius, radius, paint); + + if (offLineWidth > 0) { + final float cy = offLineWidth * 0.5f; + rect.set(spotX - cy, centerY - cy, endX + cy, centerY + cy); + paint.setColor(offColor); + canvas.drawRoundRect(rect, cy, cy, paint); + } + + rect.set(spotX - 1 - radius, centerY - radius, spotX + 1.1f + radius, centerY + radius); + paint.setColor(borderColor); + canvas.drawRoundRect(rect, radius, radius, paint); + + final float spotR = spotSize * 0.5f; + rect.set(spotX - spotR, centerY - spotR, spotX + spotR, centerY + spotR); + paint.setColor(spotColor); + canvas.drawRoundRect(rect, spotR, spotR, paint); + } + + /** + * ============================================================================================= + * The Animate + * ============================================================================================= + */ + private static final Interpolator ANIMATION_INTERPOLATOR = new DecelerateInterpolator(); + private static final int ANIMATION_DURATION = 280; + private ObjectAnimator mAnimator; + + @SuppressLint("ObsoleteSdkInt") + private void animateCheckedState(boolean newCheckedState) { + AnimatorProperty property = new AnimatorProperty(); + if (newCheckedState) { + property.color = onColor; + property.offLineWidth = 10; + property.spotX = spotMaxX; + } else { + property.color = offBorderColor; + property.offLineWidth = spotSize; + property.spotX = spotMinX; + } + + if (mAnimator == null) { + mAnimator = ObjectAnimator.ofObject(this, ANIM_VALUE, new AnimatorEvaluator(mCurProperty), property); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) + mAnimator.setAutoCancel(true); + mAnimator.setDuration(ANIMATION_DURATION); + mAnimator.setInterpolator(ANIMATION_INTERPOLATOR); + } else { + mAnimator.cancel(); + mAnimator.setObjectValues(property); + } + mAnimator.start(); + } + + /** + * ============================================================================================= + * The custom properties + * ============================================================================================= + */ + + private AnimatorProperty mCurProperty = new AnimatorProperty(); + + private void setAnimatorProperty(AnimatorProperty property) { + this.spotX = property.spotX; + this.borderColor = property.color; + this.offLineWidth = property.offLineWidth; + invalidate(); + } + + private void setAnimatorProperty(boolean isOn) { + AnimatorProperty property = mCurProperty; + if (isOn) { + property.color = onColor; + property.offLineWidth = 10; + property.spotX = spotMaxX; + } else { + property.color = offBorderColor; + property.offLineWidth = spotSize; + property.spotX = spotMinX; + } + setAnimatorProperty(property); + } + + private final static class AnimatorProperty { + private int color; + private float offLineWidth; + private float spotX; + } + + private final static class AnimatorEvaluator implements TypeEvaluator { + private final AnimatorProperty mProperty; + + public AnimatorEvaluator(AnimatorProperty property) { + mProperty = property; + } + + @Override + public AnimatorProperty evaluate(float fraction, AnimatorProperty startValue, AnimatorProperty endValue) { + // Values + mProperty.spotX = (int) (startValue.spotX + (endValue.spotX - startValue.spotX) * fraction); + + mProperty.offLineWidth = (int) (startValue.offLineWidth + (endValue.offLineWidth - startValue.offLineWidth) * (1 - fraction)); + + // Color + int startA = (startValue.color >> 24) & 0xff; + int startR = (startValue.color >> 16) & 0xff; + int startG = (startValue.color >> 8) & 0xff; + int startB = startValue.color & 0xff; + + int endA = (endValue.color >> 24) & 0xff; + int endR = (endValue.color >> 16) & 0xff; + int endG = (endValue.color >> 8) & 0xff; + int endB = endValue.color & 0xff; + + mProperty.color = (startA + (int) (fraction * (endA - startA))) << 24 | + (startR + (int) (fraction * (endR - startR))) << 16 | + (startG + (int) (fraction * (endG - startG))) << 8 | + (startB + (int) (fraction * (endB - startB))); + + return mProperty; + } + } + + private final static Property ANIM_VALUE = new Property(AnimatorProperty.class, "animValue") { + @Override + public AnimatorProperty get(ToggleButton object) { + return object.mCurProperty; + } + + @Override + public void set(ToggleButton object, AnimatorProperty value) { + object.setAnimatorProperty(value); + } + }; + + + /** + * @author ThinkPad + */ + public interface OnToggleChanged { + /** + * @param on + */ + public void onToggle(boolean on); + } + + + public void setOnToggleChanged(OnToggleChanged onToggleChanged) { + listener = onToggleChanged; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/AlignPopupWindow.java b/app/src/main/java/net/oschina/app/improve/write/AlignPopupWindow.java new file mode 100644 index 0000000000000000000000000000000000000000..1ef1bfd9a8181ae2d97da27ba208955928f8c503 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/AlignPopupWindow.java @@ -0,0 +1,99 @@ +package net.oschina.app.improve.write; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.PopupWindow; + +import net.oschina.app.R; +import net.oschina.app.improve.widget.rich.TextSection; + +/** + * 弹出H对话框 + * Created by huanghaibin on 2017/8/31. + */ + +class AlignPopupWindow extends PopupWindow implements View.OnClickListener { + + private ImageView mImageAlignLeft, mImageAlignCenter, mImageAlignRight; + private OnAlignChangeListener mListener; + + @SuppressLint("InflateParams") + AlignPopupWindow(Context context, OnAlignChangeListener listener) { + super(LayoutInflater.from(context).inflate(R.layout.popup_window_align, null), + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + setAnimationStyle(R.style.popup_anim_style_alpha); + setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + setOutsideTouchable(true); + //setFocusable(true); + this.mListener = listener; + View content = getContentView(); + mImageAlignLeft = (ImageView) content.findViewById(R.id.iv_align_left); + mImageAlignCenter = (ImageView) content.findViewById(R.id.iv_align_center); + mImageAlignRight = (ImageView) content.findViewById(R.id.iv_align_right); + mImageAlignLeft.setOnClickListener(this); + mImageAlignCenter.setOnClickListener(this); + mImageAlignRight.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.iv_align_left: + mImageAlignLeft.setSelected(!mImageAlignLeft.isSelected()); + mListener.onAlignChange(TextSection.LEFT); + DrawableCompat.setTint(mImageAlignLeft.getDrawable(), 0xff24cf5f); + DrawableCompat.setTint(mImageAlignCenter.getDrawable(), 0xFFFFFFFF); + DrawableCompat.setTint(mImageAlignRight.getDrawable(), 0xFFFFFFFF); + break; + case R.id.iv_align_center: + mImageAlignCenter.setSelected(!mImageAlignCenter.isSelected()); + mListener.onAlignChange(TextSection.CENTER); + DrawableCompat.setTint(mImageAlignLeft.getDrawable(), 0xFFFFFFFF); + DrawableCompat.setTint(mImageAlignCenter.getDrawable(), 0xff24cf5f); + DrawableCompat.setTint(mImageAlignRight.getDrawable(), 0xFFFFFFFF); + break; + case R.id.iv_align_right: + mImageAlignRight.setSelected(!mImageAlignRight.isSelected()); + mListener.onAlignChange(TextSection.RIGHT); + DrawableCompat.setTint(mImageAlignLeft.getDrawable(), 0xFFFFFFFF); + DrawableCompat.setTint(mImageAlignCenter.getDrawable(), 0xFFFFFFFF); + DrawableCompat.setTint(mImageAlignRight.getDrawable(), 0xff24cf5f); + break; + } + } + + void setStyle(TextSection section) { + switch (section.getAlignment()) { + case TextSection.LEFT: + DrawableCompat.setTint(mImageAlignLeft.getDrawable(), 0xff24cf5f); + DrawableCompat.setTint(mImageAlignCenter.getDrawable(), 0xFFFFFFFF); + DrawableCompat.setTint(mImageAlignRight.getDrawable(), 0xFFFFFFFF); + break; + case TextSection.CENTER: + DrawableCompat.setTint(mImageAlignLeft.getDrawable(), 0xFFFFFFFF); + DrawableCompat.setTint(mImageAlignCenter.getDrawable(), 0xff24cf5f); + DrawableCompat.setTint(mImageAlignRight.getDrawable(), 0xFFFFFFFF); + break; + case TextSection.RIGHT: + DrawableCompat.setTint(mImageAlignLeft.getDrawable(), 0xFFFFFFFF); + DrawableCompat.setTint(mImageAlignCenter.getDrawable(), 0xFFFFFFFF); + DrawableCompat.setTint(mImageAlignRight.getDrawable(), 0xff24cf5f); + break; + } + } + + void show(View v) { + showAsDropDown(v, 0, -2 * v.getMeasuredHeight() + 10); + } + + interface OnAlignChangeListener { + void onAlignChange(int align); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/Blog.java b/app/src/main/java/net/oschina/app/improve/write/Blog.java new file mode 100644 index 0000000000000000000000000000000000000000..97cbf83c248b2a837161703951edd8dc4a4008f1 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/Blog.java @@ -0,0 +1,111 @@ +package net.oschina.app.improve.write; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/** + * 博客对象 + * Created by huanghaibin on 2017/9/1. + */ + +public class Blog implements Serializable { + private String title; + @SerializedName("abstract") + private String summary; + private String tags; + private int system; + private int catalog; + private int canVisible; + private int canComment; + private int isStick; + private int type; + private String content; + + public Blog() { + this.type = 1; + this.isStick = 1; + this.canComment = 1; + this.canVisible = 1; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public String getTags() { + return tags; + } + + public void setTags(String tags) { + this.tags = tags; + } + + public int getSystem() { + return system; + } + + public void setSystem(int system) { + this.system = system; + } + + public int getCatalog() { + return catalog; + } + + public void setCatalog(int catalog) { + this.catalog = catalog; + } + + public int getCanVisible() { + return canVisible; + } + + public void setCanVisible(int canVisible) { + this.canVisible = canVisible; + } + + public int getCanComment() { + return canComment; + } + + public void setCanComment(int canComment) { + this.canComment = canComment; + } + + public int getIsStick() { + return isStick; + } + + public void setIsStick(int isStick) { + this.isStick = isStick; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/BlogCategory.java b/app/src/main/java/net/oschina/app/improve/write/BlogCategory.java new file mode 100644 index 0000000000000000000000000000000000000000..2f67b54f7f8ee0abb5f43a998fb55011d367ce4d --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/BlogCategory.java @@ -0,0 +1,93 @@ +package net.oschina.app.improve.write; + +import net.oschina.app.improve.detail.db.Column; +import net.oschina.app.improve.detail.db.PrimaryKey; +import net.oschina.app.improve.detail.db.Table; + +import java.io.Serializable; + +/** + * 博客分类 + * Created by huanghaibin on 2017/8/31. + */ +@SuppressWarnings("all") +@Table(tableName = "BlogCategory") +public class BlogCategory implements Serializable { + + @PrimaryKey(column = "id", autoincrement = false) + private long id; + + @Column(column = "name") + private String name; + + @Column(column = "create_time") + private String create_time; + + @Column(column = "options") + private int options; + + @Column(column = "sort_order") + private int sort_order; + + @Column(column = "space") + private long space; + + @Column(column = "type") + private int type; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCreate_time() { + return create_time; + } + + public void setCreate_time(String create_time) { + this.create_time = create_time; + } + + public int getOptions() { + return options; + } + + public void setOptions(int options) { + this.options = options; + } + + public int getSort_order() { + return sort_order; + } + + public void setSort_order(int sort_order) { + this.sort_order = sort_order; + } + + public long getSpace() { + return space; + } + + public void setSpace(long space) { + this.space = space; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/BlogCategoryAdapter.java b/app/src/main/java/net/oschina/app/improve/write/BlogCategoryAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..c75023edf80ea5fc37b3c298dd31ebb71b0e93f8 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/BlogCategoryAdapter.java @@ -0,0 +1,44 @@ +package net.oschina.app.improve.write; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; + +/** + * 博客分类 + * Created by huanghaibin on 2017/8/31. + */ + +class BlogCategoryAdapter extends BaseRecyclerAdapter { + + BlogCategoryAdapter(Context context) { + super(context, NEITHER); + mSelectedPosition = 0; + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new BlogCategoryHolder(mInflater.inflate(R.layout.item_list_category, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, BlogCategory item, int position) { + BlogCategoryHolder h = (BlogCategoryHolder) holder; + h.mTextCategory.setText(item.getName()); + h.mTextCategory.setTextColor(mSelectedPosition == position ? 0xFF24cf5f : 0xFF9A9A9A); + } + + private static class BlogCategoryHolder extends RecyclerView.ViewHolder { + TextView mTextCategory; + + BlogCategoryHolder(View itemView) { + super(itemView); + mTextCategory = (TextView) itemView.findViewById(R.id.tv_category); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/CategoryFragment.java b/app/src/main/java/net/oschina/app/improve/write/CategoryFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..a56342ece34caa103dfff2a1a5889a28a056aa70 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/CategoryFragment.java @@ -0,0 +1,136 @@ +package net.oschina.app.improve.write; + +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.View; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.media.Util; +import net.oschina.app.improve.widget.rich.RichEditLayout; +import net.oschina.app.improve.widget.rich.RichEditText; +import net.oschina.app.improve.widget.rich.TextSection; + +import java.lang.reflect.Type; +import java.util.List; + +import butterknife.Bind; +import cz.msebera.android.httpclient.Header; + +/** + * 列表界面的布局、系统分类、博客分类 + * Created by huanghaibin on 2017/8/31. + */ + +public class CategoryFragment extends BaseFragment { + + @Bind(R.id.recyclerBlog) + RecyclerView mRecyclerBlog; + + @Bind(R.id.recyclerSystem) + RecyclerView mRecyclerSystem; + + private BlogCategoryAdapter mBlogAdapter; + private SystemCategoryAdapter mSystemAdapter; + + public static CategoryFragment newInstance() { + return new CategoryFragment(); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_category; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mRecyclerBlog.setLayoutManager(new LinearLayoutManager(mContext)); + mRecyclerSystem.setLayoutManager(new LinearLayoutManager(mContext)); + mBlogAdapter = new BlogCategoryAdapter(mContext); + mSystemAdapter = new SystemCategoryAdapter(mContext); + mRecyclerSystem.setAdapter(mSystemAdapter); + mRecyclerBlog.setAdapter(mBlogAdapter); + mBlogAdapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + mBlogAdapter.setSelectedPosition(position); + } + }); + mSystemAdapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + mSystemAdapter.setSelectedPosition(position); + } + }); + } + + @Override + protected void initData() { + super.initData(); + getCategories(); + } + + + @Override + public void onResume() { + super.onResume(); + if (mRoot != null) { + if (RichEditLayout.KEYBOARD_HEIGHT != 0) { + mRoot.getLayoutParams().height = RichEditLayout.KEYBOARD_HEIGHT; + } else { + mRoot.getLayoutParams().height = Util.dipTopx(mContext, 270); + } + } + } + + long getCategoryId() { + if (mBlogAdapter != null) { + BlogCategory category = mBlogAdapter.getSelectedItem(); + if (category != null) + return category.getId(); + } + return -1; + } + + int getSystemId() { + if (mSystemAdapter != null) { + SystemCategoryAdapter.SystemCategory category = mSystemAdapter.getSelectedItem(); + if (category != null) + return category.getId(); + } + return -1; + } + + private void getCategories() { + OSChinaApi.getBlogCategories(new TextHttpResponseHandler() { + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + Log.e("onSuccess", "" + responseString); + try { + Type type = new TypeToken>>() { + }.getType(); + ResultBean> bean = new Gson().fromJson(responseString, type); + if (bean != null && bean.isSuccess()) { + mBlogAdapter.resetItem(bean.getResult()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/ColorAdapter.java b/app/src/main/java/net/oschina/app/improve/write/ColorAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..926ecd8cb5f4d7a8dfed39504065bd774412c83c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/ColorAdapter.java @@ -0,0 +1,52 @@ +package net.oschina.app.improve.write; + +import android.content.Context; +import android.graphics.Color; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; + +/** + * 字体适配器 + * Created by huanghaibin on 2017/8/14. + */ +@Deprecated +class ColorAdapter extends BaseRecyclerAdapter { + + ColorAdapter(Context context) { + super(context, NEITHER); + addItem("111111"); + addItem("4fd6aa"); + addItem("754acd"); + addItem("df45ac"); + addItem("fd313a"); + addItem("ca4a6d"); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new ColorHolder(mInflater.inflate(R.layout.item_list_color, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, String item, int position) { + ColorHolder h = (ColorHolder) holder; + h.mImageSelect.setVisibility(position == mSelectedPosition ? View.VISIBLE : View.GONE); + h.mColorView.setBackgroundColor(Color.parseColor("#" + item)); + } + + private static class ColorHolder extends RecyclerView.ViewHolder { + View mColorView; + ImageView mImageSelect; + + ColorHolder(View itemView) { + super(itemView); + mColorView = itemView.findViewById(R.id.viewColor); + mImageSelect = (ImageView) itemView.findViewById(R.id.iv_select); + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/FontFragment.java b/app/src/main/java/net/oschina/app/improve/write/FontFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..fb942becc61cccb79de76edcf04cdcf3b210299c --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/FontFragment.java @@ -0,0 +1,184 @@ +package net.oschina.app.improve.write; + +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.ImageButton; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; +import net.oschina.app.improve.base.fragments.BaseFragment; +import net.oschina.app.improve.media.Util; +import net.oschina.app.improve.widget.rich.RichEditLayout; +import net.oschina.app.improve.widget.rich.RichEditText; +import net.oschina.app.improve.widget.rich.TextSection; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 字体面板布局 + * Created by huanghaibin on 2017/8/14. + */ +@Deprecated +public class FontFragment extends BaseFragment implements RichEditText.OnSectionChangeListener, View.OnClickListener { + + @Bind(R.id.recyclerSize) + RecyclerView mRecyclerTitle; + @Bind(R.id.recyclerColor) + RecyclerView mRecyclerColor; + private ColorAdapter mColorAdapter; + private TitleAdapter mTitleAdapter; + + @Bind(R.id.btn_bold) + ImageButton mBtnBold; + @Bind(R.id.btn_italic) + ImageButton mBtnItalic; + @Bind(R.id.btn_mid_line) + ImageButton mBtnMidLine; + @Bind(R.id.btn_align_left) + ImageButton mBtnAlignLeft; + @Bind(R.id.btn_align_center) + ImageButton mBtnAlignCenter; + @Bind(R.id.btn_align_right) + ImageButton mBtnAlignRight; + + private OnFontStyleChangeListener mListener; + + static FontFragment newInstance() { + return new FontFragment(); + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_font; + } + + @Override + protected void initWidget(View root) { + super.initWidget(root); + mColorAdapter = new ColorAdapter(mContext); + mRecyclerColor.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false)); + mRecyclerColor.setAdapter(mColorAdapter); + mRecyclerColor.addItemDecoration(new GridItemDecoration(Util.dipTopx(mContext, 16))); + mColorAdapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + mColorAdapter.setSelectedPosition(position); + if (mListener != null) + mListener.onColorChange(mColorAdapter.getItem(position)); + } + }); + + mTitleAdapter = new TitleAdapter(mContext); + mRecyclerTitle.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false)); + mRecyclerTitle.setAdapter(mTitleAdapter); + mRecyclerTitle.addItemDecoration(new GridItemDecoration(Util.dipTopx(mContext, 16))); + mTitleAdapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() { + @Override + public void onItemClick(int position, long itemId) { + mTitleAdapter.setSelectedPosition(position); + if (mListener != null) + mListener.onTitleChange(mTitleAdapter.getItem(position)); + } + }); + mListener = (OnFontStyleChangeListener) mContext; + } + + @OnClick({R.id.btn_bold,R.id.btn_italic,R.id.btn_mid_line, + R.id.btn_align_left,R.id.btn_align_center,R.id.btn_align_right}) + @Override + public void onClick(View v) { + if (mListener == null) + return; + switch (v.getId()) { + case R.id.btn_bold: + mBtnBold.setSelected(!mBtnBold.isSelected()); + mListener.onBoldChange(mBtnBold.isSelected()); + DrawableCompat.setTint(mBtnBold.getDrawable(), mBtnBold.isSelected() ? 0xff24cf5f : 0xFFFFFFFF); + break; + case R.id.btn_italic: + mBtnItalic.setSelected(!mBtnItalic.isSelected()); + mListener.onItalicChange(mBtnItalic.isSelected()); + DrawableCompat.setTint(mBtnItalic.getDrawable(), mBtnItalic.isSelected() ? 0xff24cf5f : 0xFFFFFFFF); + break; + case R.id.btn_mid_line: + mBtnMidLine.setSelected(!mBtnMidLine.isSelected()); + mListener.onMidLineChange(mBtnMidLine.isSelected()); + DrawableCompat.setTint(mBtnMidLine.getDrawable(), mBtnMidLine.isSelected() ? 0xff24cf5f : 0xFFFFFFFF); + break; + case R.id.btn_align_left: + mBtnAlignLeft.setSelected(!mBtnAlignLeft.isSelected()); + mListener.onAlignChange(TextSection.LEFT); + DrawableCompat.setTint(mBtnAlignLeft.getDrawable(), 0xff24cf5f); + DrawableCompat.setTint(mBtnAlignCenter.getDrawable(), 0xFFFFFFFF); + DrawableCompat.setTint(mBtnAlignRight.getDrawable(), 0xFFFFFFFF); + break; + case R.id.btn_align_center: + mBtnAlignCenter.setSelected(!mBtnAlignCenter.isSelected()); + mListener.onAlignChange(TextSection.CENTER); + DrawableCompat.setTint(mBtnAlignLeft.getDrawable(), 0xFFFFFFFF); + DrawableCompat.setTint(mBtnAlignCenter.getDrawable(), 0xff24cf5f); + DrawableCompat.setTint(mBtnAlignRight.getDrawable(), 0xFFFFFFFF); + break; + case R.id.btn_align_right: + mBtnAlignRight.setSelected(!mBtnAlignRight.isSelected()); + mListener.onAlignChange(TextSection.RIGHT); + DrawableCompat.setTint(mBtnAlignLeft.getDrawable(), 0xff111111); + DrawableCompat.setTint(mBtnAlignCenter.getDrawable(), 0xff111111); + DrawableCompat.setTint(mBtnAlignRight.getDrawable(), 0xff24cf5f); + break; + + } + } + + @Override + public void onSectionChange(TextSection section) { + if (mRoot == null) + return; + DrawableCompat.setTint(mBtnBold.getDrawable(), section.isBold() ? 0xff24cf5f : 0xff111111); + DrawableCompat.setTint(mBtnItalic.getDrawable(), section.isItalic() ? 0xff24cf5f : 0xff111111); + DrawableCompat.setTint(mBtnMidLine.getDrawable(), section.isMidLine() ? 0xff24cf5f : 0xff111111); + + if (section.getAlignment() == TextSection.LEFT) { + DrawableCompat.setTint(mBtnAlignLeft.getDrawable(), 0xff24cf5f); + DrawableCompat.setTint(mBtnAlignCenter.getDrawable(), 0xff111111); + DrawableCompat.setTint(mBtnAlignRight.getDrawable(), 0xff111111); + } else if (section.getAlignment() == TextSection.CENTER) { + DrawableCompat.setTint(mBtnAlignLeft.getDrawable(), 0xff111111); + DrawableCompat.setTint(mBtnAlignCenter.getDrawable(), 0xff24cf5f); + DrawableCompat.setTint(mBtnAlignRight.getDrawable(), 0xff111111); + } else if (section.getAlignment() == TextSection.RIGHT) { + DrawableCompat.setTint(mBtnAlignLeft.getDrawable(), 0xff111111); + DrawableCompat.setTint(mBtnAlignCenter.getDrawable(), 0xff111111); + DrawableCompat.setTint(mBtnAlignRight.getDrawable(), 0xff24cf5f); + } + } + + @Override + public void onResume() { + super.onResume(); + if (mRoot != null) { + if (RichEditLayout.KEYBOARD_HEIGHT != 0) { + mRoot.getLayoutParams().height = RichEditLayout.KEYBOARD_HEIGHT; + } else { + mRoot.getLayoutParams().height = Util.dipTopx(mContext, 270); + } + } + } + + interface OnFontStyleChangeListener { + void onBoldChange(boolean isBold); + + void onItalicChange(boolean isItalic); + + void onMidLineChange(boolean isMidLine); + + void onAlignChange(int align); + + void onTitleChange(TitleAdapter.Title title); + + void onColorChange(String color); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/FontPopupWindow.java b/app/src/main/java/net/oschina/app/improve/write/FontPopupWindow.java new file mode 100644 index 0000000000000000000000000000000000000000..606c0b3df8c2f128063d8095d567b97da8344171 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/FontPopupWindow.java @@ -0,0 +1,84 @@ +package net.oschina.app.improve.write; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.PopupWindow; + +import net.oschina.app.R; +import net.oschina.app.improve.widget.rich.TextSection; + +/** + * 弹出H对话框 + * Created by huanghaibin on 2017/8/31. + */ + +class FontPopupWindow extends PopupWindow implements View.OnClickListener{ + + private ImageView mImageBold, mImageItalic, mImageLine; + private OnFontChangeListener mListener; + + @SuppressLint("InflateParams") + FontPopupWindow(Context context,OnFontChangeListener listener) { + super(LayoutInflater.from(context).inflate(R.layout.popup_window_font, null), + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + setAnimationStyle(R.style.popup_anim_style_alpha); + setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + setOutsideTouchable(true); + //setFocusable(true); + + this.mListener = listener; + View content = getContentView(); + mImageBold = (ImageView) content.findViewById(R.id.iv_font_bold); + mImageItalic = (ImageView) content.findViewById(R.id.iv_font_italic); + mImageLine = (ImageView) content.findViewById(R.id.iv_font_line); + mImageBold.setOnClickListener(this); + mImageItalic.setOnClickListener(this); + mImageLine.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.iv_font_bold: + mImageBold.setSelected(!mImageBold.isSelected()); + mListener.onBoldChange(mImageBold.isSelected()); + DrawableCompat.setTint(mImageBold.getDrawable(), mImageBold.isSelected() ? 0xff24cf5f : 0xFFFFFFFF); + break; + case R.id.iv_font_italic: + mImageItalic.setSelected(!mImageItalic.isSelected()); + mListener.onItalicChange(mImageItalic.isSelected()); + DrawableCompat.setTint(mImageItalic.getDrawable(), mImageItalic.isSelected() ? 0xff24cf5f : 0xFFFFFFFF); + break; + case R.id.iv_font_line: + mImageLine.setSelected(!mImageLine.isSelected()); + mListener.onMidLineChange(mImageLine.isSelected()); + DrawableCompat.setTint(mImageLine.getDrawable(), mImageLine.isSelected() ? 0xff24cf5f : 0xFFFFFFFF); + break; + } + } + + void setStyle(TextSection section) { + DrawableCompat.setTint(mImageBold.getDrawable(), section.isBold() ? 0xff24cf5f : 0xFFFFFFFF); + DrawableCompat.setTint(mImageItalic.getDrawable(), section.isItalic() ? 0xff24cf5f : 0xFFFFFFFF); + DrawableCompat.setTint(mImageLine.getDrawable(), section.isMidLine() ? 0xff24cf5f : 0xFFFFFFFF); + } + + void show(View v) { + showAsDropDown(v, 0, -2 * v.getMeasuredHeight() + 10); + } + + interface OnFontChangeListener { + void onBoldChange(boolean isBold); + + void onItalicChange(boolean isItalic); + + void onMidLineChange(boolean isMidLine); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/GridItemDecoration.java b/app/src/main/java/net/oschina/app/improve/write/GridItemDecoration.java new file mode 100644 index 0000000000000000000000000000000000000000..87b253e5551170dec0f04e24aa64d4082f6686a5 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/GridItemDecoration.java @@ -0,0 +1,24 @@ +package net.oschina.app.improve.write; + +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * 间隔 + * Created by huanghaibin on 2017/8/14. + */ +@Deprecated +class GridItemDecoration extends RecyclerView.ItemDecoration { + private int space; + + GridItemDecoration(int space) { + this.space = space; + } + + @Override + public void getItemOffsets(Rect outRect, View view, + RecyclerView parent, RecyclerView.State state) { + outRect.right = space; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/HPopupWindow.java b/app/src/main/java/net/oschina/app/improve/write/HPopupWindow.java new file mode 100644 index 0000000000000000000000000000000000000000..c567839ae81b571e97a3e37e53d1a2cbb760e484 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/HPopupWindow.java @@ -0,0 +1,95 @@ +package net.oschina.app.improve.write; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.PopupWindow; + +import net.oschina.app.R; +import net.oschina.app.improve.widget.rich.TextSection; + +/** + * 弹出H对话框 + * Created by huanghaibin on 2017/8/31. + */ + +class HPopupWindow extends PopupWindow implements View.OnClickListener { + private OnHeaderChangeListener mListener; + private ImageView mImageH1, mImageH2, mImageH3; + + @SuppressLint("InflateParams") + HPopupWindow(Context context, OnHeaderChangeListener listener) { + super(LayoutInflater.from(context).inflate(R.layout.popup_window_h, null), + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + setAnimationStyle(R.style.popup_anim_style_alpha); + setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + setOutsideTouchable(true); + setFocusable(false); + this.mListener = listener; + View content = getContentView(); + + mImageH1 = (ImageView) content.findViewById(R.id.iv_h1); + mImageH2 = (ImageView) content.findViewById(R.id.iv_h2); + mImageH3 = (ImageView) content.findViewById(R.id.iv_h3); + + mImageH1.setOnClickListener(this); + mImageH2.setOnClickListener(this); + mImageH3.setOnClickListener(this); + } + + void setStyle(TextSection section) { + DrawableCompat.setTint(mImageH1.getDrawable(), 0xFFFFFFFF); + DrawableCompat.setTint(mImageH2.getDrawable(), 0xFFFFFFFF); + DrawableCompat.setTint(mImageH3.getDrawable(), 0xFFFFFFFF); + switch (section.getTextSize()) { + case 28: + DrawableCompat.setTint(mImageH1.getDrawable(), 0xff24cf5f); + break; + case 24: + DrawableCompat.setTint(mImageH2.getDrawable(), 0xff24cf5f); + break; + case 20: + DrawableCompat.setTint(mImageH3.getDrawable(), 0xff24cf5f); + break; + } + } + + + @Override + public void onClick(View v) { + + switch (v.getId()) { + case R.id.iv_h1: + mImageH1.setSelected(!mImageH1.isSelected()); + mListener.onTitleChange(mImageH1.isSelected() ? 28 : 18); + DrawableCompat.setTint(mImageH1.getDrawable(), mImageH1.isSelected() ? 0xff24cf5f : 0xFFFFFFFF); + + break; + case R.id.iv_h2: + mImageH2.setSelected(!mImageH2.isSelected()); + mListener.onTitleChange(mImageH2.isSelected() ? 24 : 18); + DrawableCompat.setTint(mImageH2.getDrawable(), mImageH2.isSelected() ? 0xff24cf5f : 0xFFFFFFFF); + + break; + case R.id.iv_h3: + mImageH3.setSelected(!mImageH3.isSelected()); + mListener.onTitleChange(mImageH3.isSelected() ? 20 : 18); + DrawableCompat.setTint(mImageH3.getDrawable(), mImageH3.isSelected() ? 0xff24cf5f : 0xFFFFFFFF); + break; + } + } + + void show(View v) { + showAsDropDown(v, 0, -2 * v.getMeasuredHeight() + 10); + } + + interface OnHeaderChangeListener { + void onTitleChange(int size); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/SystemCategoryAdapter.java b/app/src/main/java/net/oschina/app/improve/write/SystemCategoryAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..ae3735bd7c2329bf53a3c0c12af05c2163a73c98 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/SystemCategoryAdapter.java @@ -0,0 +1,93 @@ +package net.oschina.app.improve.write; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; + +import java.util.ArrayList; +import java.util.List; + +/** + * 系统分类 + * Created by huanghaibin on 2017/8/31. + */ + +class SystemCategoryAdapter extends BaseRecyclerAdapter { + SystemCategoryAdapter(Context context) { + super(context, NEITHER); + addAll(getCategories()); + mSelectedPosition = 0; + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new SystemCategoryHolder(mInflater.inflate(R.layout.item_list_category, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, SystemCategory item, int position) { + SystemCategoryHolder h = (SystemCategoryHolder) holder; + h.mTextCategory.setText(item.getName()); + h.mTextCategory.setTextColor(mSelectedPosition == position ? 0xFF24cf5f : 0xFF9A9A9A); + } + + static class SystemCategory { + private int id; + private String name; + + SystemCategory(int id, String name) { + this.id = id; + this.name = name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + private static class SystemCategoryHolder extends RecyclerView.ViewHolder { + TextView mTextCategory; + + SystemCategoryHolder(View itemView) { + super(itemView); + mTextCategory = (TextView) itemView.findViewById(R.id.tv_category); + } + } + + private static List getCategories() { + List categories = new ArrayList<>(); + categories.add(new SystemCategory(428602, "移动开发")); + categories.add(new SystemCategory(428612, "前端开发")); + categories.add(new SystemCategory(5611447, "人工智能")); + categories.add(new SystemCategory(428640, "服务端开发/管理")); + categories.add(new SystemCategory(429511, "游戏开发")); + categories.add(new SystemCategory(428609, "编程语言")); + categories.add(new SystemCategory(428610, "数据库")); + categories.add(new SystemCategory(428611, "企业开发")); + categories.add(new SystemCategory(428647, "图像/多媒体")); + categories.add(new SystemCategory(428613, "系统运维")); + categories.add(new SystemCategory(428638, "软件工程")); + categories.add(new SystemCategory(5593654, "大数据")); + categories.add(new SystemCategory(428639, "云计算")); + categories.add(new SystemCategory(430884, "开源硬件")); + categories.add(new SystemCategory(430381, "其他类型")); + return categories; + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/TitleAdapter.java b/app/src/main/java/net/oschina/app/improve/write/TitleAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..f430fdf1aaae1727c2481bed46d1532f35c5d883 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/TitleAdapter.java @@ -0,0 +1,87 @@ +package net.oschina.app.improve.write; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.improve.base.adapter.BaseRecyclerAdapter; + +import java.io.Serializable; + +/** + * 字体适配器 + * Created by huanghaibin on 2017/8/14. + */ +@Deprecated +class TitleAdapter extends BaseRecyclerAdapter { + private int mSelectedPosition = -0; + + TitleAdapter(Context context) { + super(context, NEITHER); + addItem(new Title(16, "普通")); + addItem(new Title(36, "标题1")); + addItem(new Title(28, "标题2")); + addItem(new Title(20, "标题3")); + addItem(new Title(14, "标题4")); + } + + @Override + protected RecyclerView.ViewHolder onCreateDefaultViewHolder(ViewGroup parent, int type) { + return new TitleHolder(mInflater.inflate(R.layout.item_list_title, parent, false)); + } + + @Override + protected void onBindDefaultViewHolder(RecyclerView.ViewHolder holder, TitleAdapter.Title item, int position) { + TitleHolder h = (TitleHolder) holder; + h.mTextTitle.setText(item.getText()); + h.mTextTitle.setTextSize(item.getSize()); + h.mTextTitle.setTextColor(position == mSelectedPosition ? 0xff24cf5f : 0xff111111); + } + + private static class TitleHolder extends RecyclerView.ViewHolder { + TextView mTextTitle; + + TitleHolder(View itemView) { + super(itemView); + mTextTitle = (TextView) itemView.findViewById(R.id.tv_title); + } + } + + static class Title implements Serializable { + private int size; + private String text; + private int type; + + public Title(int size, String text) { + this.size = size; + this.text = text; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/TriangleLayout.java b/app/src/main/java/net/oschina/app/improve/write/TriangleLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..1c68366febf614fefe6f0a818ce0456c78318630 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/TriangleLayout.java @@ -0,0 +1,43 @@ +package net.oschina.app.improve.write; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; + +/** + * 例文评论布局,三角形下标 + * Created by huanghaibin on 2017/7/12. + */ + +public class TriangleLayout extends View { + + protected Paint mTextPaint; + + public TriangleLayout(Context context) { + this(context, null); + } + + public TriangleLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mTextPaint = new Paint(); + mTextPaint.setColor(0xFF000000); + mTextPaint.setStyle(Paint.Style.FILL); + mTextPaint.setAntiAlias(true); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + @SuppressLint("DrawAllocation") Path path = new Path(); + path.lineTo(0, 0); + path.lineTo(getMeasuredWidth(), 0); + path.lineTo(getMeasuredWidth() / 2, getHeight()); + path.close(); + canvas.drawPath(path, mTextPaint); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/WriteActivity.java b/app/src/main/java/net/oschina/app/improve/write/WriteActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..3b835a9c53dfcd1ee900b375e90cbaf84abd27a5 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/WriteActivity.java @@ -0,0 +1,251 @@ +package net.oschina.app.improve.write; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Handler; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.FrameLayout; + +import net.oschina.app.R; +import net.oschina.app.improve.base.activities.BackActivity; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.utils.DialogHelper; +import net.oschina.app.improve.widget.SimplexToast; +import net.oschina.app.improve.widget.rich.RichEditLayout; +import net.oschina.app.improve.widget.rich.RichEditText; +import net.oschina.app.improve.widget.rich.TextSection; + +import java.util.List; + +import butterknife.Bind; +import butterknife.OnClick; + +/** + * 写博客界面 + * Created by huanghaibin on 2017/8/3. + */ + +public class WriteActivity extends BackActivity implements + View.OnClickListener, FontPopupWindow.OnFontChangeListener, + AlignPopupWindow.OnAlignChangeListener, RichEditText.OnSectionChangeListener, + HPopupWindow.OnHeaderChangeListener, + WriteContract.View { + + @Bind(R.id.fl_content) + FrameLayout mFrameContent; + + @Bind(R.id.richLayout) + RichEditLayout mEditView; + + private CategoryFragment mCategoryFragment; + private AlignPopupWindow mAlignWindow; + private HPopupWindow mHPopupWindow; + private FontPopupWindow mFontPopupWindow; + private Handler mHandler = new Handler(); + + private WritePresenter mPresenter; + + private Blog mBlog = new Blog(); + + public static void show(Context context) { + context.startActivity(new Intent(context, WriteActivity.class)); + } + + @Override + protected int getContentView() { + return R.layout.activity_write; + } + + + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return false; + } + + @Override + protected void initWidget() { + super.initWidget(); + setStatusBarDarkMode(); + setDarkToolBar(); + mEditView.setContentPanel(mFrameContent); + mCategoryFragment = CategoryFragment.newInstance(); + addFragment(R.id.fl_content, mCategoryFragment); + mEditView.setOnSectionChangeListener(this); + mAlignWindow = new AlignPopupWindow(this, this); + mFontPopupWindow = new FontPopupWindow(this, this); + mHPopupWindow = new HPopupWindow(this, this); + } + + @Override + protected void initData() { + super.initData(); + mPresenter = new WritePresenter(this); + } + + @OnClick({R.id.btn_keyboard, R.id.btn_font, R.id.btn_align, R.id.btn_h, + R.id.btn_category}) + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_keyboard: + if (mEditView.isKeyboardOpen()) { + mHandler.removeCallbacksAndMessages(null); + mEditView.closeKeyboard(); + mFrameContent.setVisibility(View.GONE); + mEditView.setCategoryTint(0xff111111); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + mEditView.setAdjustResize(); + } + }, 150); + } else { + if (mFrameContent.getVisibility() == View.VISIBLE) { + mEditView.setAdjustNothing(); + mEditView.setCategoryTint(0xff111111); + } else { + mEditView.setAdjustResize(); + } + mEditView.openKeyboard(); + } + break; + case R.id.btn_category: + mEditView.setAdjustNothing(); + if (mFrameContent.getVisibility() == View.VISIBLE) { + if (mEditView.isKeyboardOpen()) { + mEditView.closeKeyboard(); + addFragment(R.id.fl_content, mCategoryFragment); + mFrameContent.setVisibility(View.VISIBLE); + mEditView.setCategoryTint(0xff24cf5f); + } else { + addFragment(R.id.fl_content, mCategoryFragment); + mFrameContent.setVisibility(View.GONE); + mEditView.setCategoryTint(0xff111111); + mEditView.closeKeyboard(); + } + + } else { + mEditView.closeKeyboard(); + addFragment(R.id.fl_content, mCategoryFragment); + mFrameContent.setVisibility(View.VISIBLE); + mEditView.setCategoryTint(0xff24cf5f); + } + break; + case R.id.btn_font: + mFontPopupWindow.show(v); + break; + case R.id.btn_h: + mHPopupWindow.show(v); + break; + case R.id.btn_align: + mAlignWindow.show(v); + break; + + } + } + + @Override + public void onSectionChange(TextSection section) { + mAlignWindow.setStyle(section); + mFontPopupWindow.setStyle(section); + mHPopupWindow.setStyle(section); + } + + + @Override + public void onBoldChange(boolean isBold) { + mEditView.setBold(isBold); + } + + @Override + public void onItalicChange(boolean isItalic) { + mEditView.setItalic(isItalic); + } + + @Override + public void onMidLineChange(boolean isMidLine) { + mEditView.setMidLine(isMidLine); + } + + @Override + public void onAlignChange(int align) { + mEditView.setAlignStyle(align); + } + + @Override + public void onTitleChange(int size) { + mEditView.setTextSize(size); + } + + + @Override + public void showNetworkError(int strId) { + if (isDestroy()) return; + SimplexToast.show(this, strId); + } + + @Override + public void showPubBlogSuccess(int strId, SubBean bean) { + if (isDestroy()) return; + SimplexToast.show(this, strId); + dismissLoadingDialog(); + finish(); + } + + @Override + public void showPubBlogFailure(int strId) { + if (isDestroy()) return; + SimplexToast.show(this, strId); + dismissLoadingDialog(); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_commit, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_commit) { + showLoadingDialog("正在发布博客..."); + //mBlog.setSummary(mEditView.getSummary()); + mBlog.setTitle(mEditView.getTitle()); + mBlog.setCatalog((int) mCategoryFragment.getCategoryId()); + mBlog.setSystem(mCategoryFragment.getSystemId()); + List sections = mEditView.createSectionList(); + mPresenter.pubBlog(mBlog, sections); + } + return false; + } + + @Override + public void onBackPressed() { + if (mEditView.isEmpty()) { + super.onBackPressed(); + } else { + DialogHelper.getConfirmDialog(this, + "你还没保存博客", + "确定要退出吗", + "确定", + "取消", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }).show(); + } + } + + @Override + public void setPresenter(WriteContract.Presenter presenter) { + + } + +} diff --git a/app/src/main/java/net/oschina/app/improve/write/WriteContract.java b/app/src/main/java/net/oschina/app/improve/write/WriteContract.java new file mode 100644 index 0000000000000000000000000000000000000000..955d93e20e81c75e36dfc3fdb4a66c01821c0993 --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/WriteContract.java @@ -0,0 +1,31 @@ +package net.oschina.app.improve.write; + +import net.oschina.app.improve.base.BasePresenter; +import net.oschina.app.improve.base.BaseView; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.widget.rich.TextSection; + +import java.util.List; + +/** + * 写博客 + * Created by huanghaibin on 2017/8/3. + */ + +interface WriteContract { + + interface View extends BaseView { + void showPubBlogSuccess(int strId, SubBean bean); + + void showPubBlogFailure(int strId); + } + + interface Presenter extends BasePresenter { + /** + * 发布博客 + * + * @param blog 标题 + */ + void pubBlog(Blog blog, List sections); + } +} diff --git a/app/src/main/java/net/oschina/app/improve/write/WritePresenter.java b/app/src/main/java/net/oschina/app/improve/write/WritePresenter.java new file mode 100644 index 0000000000000000000000000000000000000000..387094893564efda0896073c61b4a75b0a43b37e --- /dev/null +++ b/app/src/main/java/net/oschina/app/improve/write/WritePresenter.java @@ -0,0 +1,218 @@ +package net.oschina.app.improve.write; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.loopj.android.http.TextHttpResponseHandler; + +import net.oschina.app.R; +import net.oschina.app.api.remote.OSChinaApi; +import net.oschina.app.improve.bean.SubBean; +import net.oschina.app.improve.bean.base.ResultBean; +import net.oschina.app.improve.widget.rich.TextSection; + +import java.lang.reflect.Type; +import java.util.List; + +import cz.msebera.android.httpclient.Header; + +/** + * 发布博客 + * Created by huanghaibin on 2017/8/3. + */ + +class WritePresenter implements WriteContract.Presenter { + private final WriteContract.View mView; + private boolean isLoading; + + WritePresenter(WriteContract.View mView) { + this.mView = mView; + this.mView.setPresenter(this); + } + + @Override + public void pubBlog(Blog blog, List sections) { + if (checkIsEmpty(sections)) { + mView.showPubBlogFailure(R.string.blog_content_empty_error); + return; + } + if (isLoading) + return; + if (blog == null) { + mView.showPubBlogFailure(R.string.blog_empty_error); + return; + } + if (TextUtils.isEmpty(blog.getTitle())) { + mView.showPubBlogFailure(R.string.blog_title_empty_error); + return; + } +// if (TextUtils.isEmpty(blog.getSummary())) { +// mView.showPubBlogFailure(R.string.blog_summary_empty_error); +// return; +// } + blog.setContent(getContent(sections)); + if (TextUtils.isEmpty(blog.getContent())) { + mView.showPubBlogFailure(R.string.blog_content_empty_error); + return; + } + + isLoading = true; + OSChinaApi.pubBlog(blog, new TextHttpResponseHandler() { + + @Override + public void onFinish() { + super.onFinish(); + isLoading = false; + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + mView.showPubBlogFailure(R.string.pub_blog_failure); + } + + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + try { + Type type = new TypeToken>() { + }.getType(); + ResultBean bean = new Gson().fromJson(responseString, type); + if (bean.isSuccess()) { + mView.showPubBlogSuccess(R.string.pub_blog_success, bean.getResult()); + } else { + mView.showPubBlogFailure(R.string.pub_blog_failure); + } + } catch (Exception e) { + e.printStackTrace(); + mView.showPubBlogFailure(R.string.pub_blog_failure); + } + } + }); + } + + private String getContent(List sections) { + if (sections == null || sections.size() == 0) + return null; + StringBuilder sb = new StringBuilder(); + for (TextSection section : sections) { + if (TextUtils.isEmpty(section.getText())) { + sb.append("

     

    "); + continue; + } + if (section.isHeader()) + sb.append(getHeaderHtml(section)); + else + sb.append(getContentHtml(section)); + sb.append("\n"); + } + return sb.toString(); + } + + private String getHeaderHtml(TextSection section) { + StringBuilder sb = new StringBuilder(); + insertH(sb, section.getTextSize(), true); + if (section.isBold()) { + sb.append(""); + } + if (section.isItalic()) { + sb.append(""); + } + if (section.isMidLine()) { + sb.append(""); + } + sb.append(formatHtml(section.getText())); + if (section.isMidLine()) { + sb.append(""); + } + if (section.isItalic()) { + sb.append(""); + } + if (section.isBold()) { + sb.append(""); + } + insertH(sb, section.getTextSize(), false); + return sb.toString(); + } + + + private void insertH(StringBuilder sb, int size, boolean isLeft) { + switch (size) { + case 28: + sb.append(isLeft ? "

    " : "

    "); + break; + case 24: + sb.append(isLeft ? "

    " : "

    "); + break; + case 20: + sb.append(isLeft ? "

    " : "

    "); + break; + case 16: + sb.append(isLeft ? "

    " : "

    "); + break; + } + } + + private String getContentHtml(TextSection section) { + StringBuilder sb = new StringBuilder(); + sb.append(String.format("

    ", getAlign(section.getAlignment()))); + if (section.isBold()) { + + sb.append(""); + } + if (section.isItalic()) { + + sb.append(""); + } + if (section.isMidLine()) { + sb.append(""); + } + sb.append(formatHtml(section.getText())); + + if (section.isMidLine()) { + sb.append(""); + } + if (section.isItalic()) { + + sb.append(""); + } + if (section.isBold()) { + + sb.append(""); + } + + sb.append("

    "); + return sb.toString(); + } + + private String getAlign(int align) { + if (align == TextSection.LEFT) + return "left"; + else if (align == TextSection.CENTER) + return "center"; + else return + "right"; + } + + private String formatHtml(String content) { + return content.replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">"); + } + + /** + * 检查是否有输入 + * + * @param sections 全部段落 + * @return 输入是否为空 + */ + private static boolean checkIsEmpty(List sections) { + if (sections == null || sections.size() == 0) + return true; + for (TextSection section : sections) { + if (!TextUtils.isEmpty(section.getText().trim())) + return false; + + } + return true; + } +} diff --git a/app/src/main/java/net/oschina/app/interf/BaseFragmentInterface.java b/app/src/main/java/net/oschina/app/interf/BaseFragmentInterface.java index 486c930351c5fd95d081d96b7f4d3f29817564cc..9d4fdd6c549e6a05426d06815b22157a0e9f4f98 100644 --- a/app/src/main/java/net/oschina/app/interf/BaseFragmentInterface.java +++ b/app/src/main/java/net/oschina/app/interf/BaseFragmentInterface.java @@ -5,13 +5,13 @@ import android.view.View; /** * 基类fragment实现接口 + * * @author FireAnt(http://my.oschina.net/LittleDY) * @created 2014年9月25日 上午11:00:25 - * */ public interface BaseFragmentInterface { - - public void initView(View view); - - public void initData(); + + public void initView(View view); + + public void initData(); } diff --git a/app/src/main/java/net/oschina/app/interf/BaseViewInterface.java b/app/src/main/java/net/oschina/app/interf/BaseViewInterface.java index 2ea5be2dffb139f156ab3a90490df03576febbd6..e27c60bc0cf6437bb0d867a18d8ffdb8da6e67ca 100644 --- a/app/src/main/java/net/oschina/app/interf/BaseViewInterface.java +++ b/app/src/main/java/net/oschina/app/interf/BaseViewInterface.java @@ -1,14 +1,12 @@ package net.oschina.app.interf; /** - * * @author deyi - * */ public interface BaseViewInterface { - - public void initView(); - - public void initData(); - + + public void initView(); + + public void initData(); + } diff --git a/app/src/main/java/net/oschina/app/interf/ICallbackResult.java b/app/src/main/java/net/oschina/app/interf/ICallbackResult.java deleted file mode 100644 index 929f80c080c5fbf696a60f6dd8bf92a0662e9c1a..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/interf/ICallbackResult.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.oschina.app.interf; - -/** - * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2014年11月18日 上午11:18:28 - * - */ - -public interface ICallbackResult { - - public void OnBackResult(Object s); -} diff --git a/app/src/main/java/net/oschina/app/interf/OnTabReselectListener.java b/app/src/main/java/net/oschina/app/interf/OnTabReselectListener.java index ca491922c7a6e173b6b34fed82729cb9252f42e3..b832d866213df2b1a3c651338006abce71791224 100644 --- a/app/src/main/java/net/oschina/app/interf/OnTabReselectListener.java +++ b/app/src/main/java/net/oschina/app/interf/OnTabReselectListener.java @@ -1,12 +1,12 @@ package net.oschina.app.interf; -/** +/** * 当tabHost再次被点击时 + * * @author FireAnt(http://my.oschina.net/LittleDY) - * @version 创建时间:2014年11月17日 上午11:00:15 - * + * @version 创建时间:2014年11月17日 上午11:00:15 */ public interface OnTabReselectListener { - - public void onTabReselect(); + + void onTabReselect(); } diff --git a/app/src/main/java/net/oschina/app/interf/OnWebViewImageListener.java b/app/src/main/java/net/oschina/app/interf/OnWebViewImageListener.java index 7ea96a5764ad466ad8c7bfc5a7c8d557bf331745..32ef3ea4820ab06b7d1a11581c400c50090dfd1c 100644 --- a/app/src/main/java/net/oschina/app/interf/OnWebViewImageListener.java +++ b/app/src/main/java/net/oschina/app/interf/OnWebViewImageListener.java @@ -2,16 +2,16 @@ package net.oschina.app.interf; /** * 监听webview上的图片 - * - * @author yeguozhong@yeah.net * + * @author yeguozhong@yeah.net */ public interface OnWebViewImageListener { - /** - * 点击webview上的图片,传入该缩略图的大图Url - * @param bigImageUrl - */ - void showImagePreview(String bigImageUrl); - + /** + * 点击webview上的图片,传入该缩略图的大图Url + * + * @param bigImageUrl + */ + void showImagePreview(String bigImageUrl); + } diff --git a/app/src/main/java/net/oschina/app/service/DownloadService.java b/app/src/main/java/net/oschina/app/service/DownloadService.java deleted file mode 100644 index 9da874a9cdb3a09f3c8d552b865bbce5ad5a3657..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/service/DownloadService.java +++ /dev/null @@ -1,302 +0,0 @@ -package net.oschina.app.service; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; - -import net.oschina.app.AppConfig; -import net.oschina.app.R; -import net.oschina.app.interf.ICallbackResult; -import net.oschina.app.ui.MainActivity; -import net.oschina.app.util.StringUtils; -import net.oschina.app.util.TDevice; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.widget.RemoteViews; - -/** - * download service - * - * @author FireAnt(http://my.oschina.net/LittleDY) - * @created 2014年11月18日 下午3:02:36 - * - */ -public class DownloadService extends Service { - - public static final String BUNDLE_KEY_DOWNLOAD_URL = "download_url"; - - public static final String BUNDLE_KEY_TITLE = "title"; - - private final String tag = "download"; - private static final int NOTIFY_ID = 0; - private int progress; - private NotificationManager mNotificationManager; - private boolean canceled; - - private String downloadUrl; - - private String mTitle = "正在下载%s"; - - private String saveFileName = AppConfig.DEFAULT_SAVE_FILE_PATH; - - private ICallbackResult callback; - - private DownloadBinder binder; - - private boolean serviceIsDestroy = false; - - private Context mContext = this; - - private Thread downLoadThread; - - private Notification mNotification; - - private Handler mHandler = new Handler() { - - @Override - public void handleMessage(Message msg) { - // TODO Auto-generated method stub - super.handleMessage(msg); - switch (msg.what) { - case 0: - // 下载完毕 - mNotificationManager.cancel(NOTIFY_ID); - installApk(); - break; - case 2: - // 取消通知 - mNotificationManager.cancel(NOTIFY_ID); - break; - case 1: - int rate = msg.arg1; - if (rate < 100) { - RemoteViews contentview = mNotification.contentView; - contentview.setTextViewText(R.id.tv_download_state, mTitle + "(" + rate - + "%" + ")"); - contentview.setProgressBar(R.id.pb_download, 100, rate, - false); - } else { - // 下载完毕后变换通知形式 - mNotification.flags = Notification.FLAG_AUTO_CANCEL; - mNotification.contentView = null; - Intent intent = new Intent(mContext, MainActivity.class); - // 告知已完成 - intent.putExtra("completed", "yes"); - // 更新参数,注意flags要使用FLAG_UPDATE_CURRENT - PendingIntent contentIntent = PendingIntent.getActivity( - mContext, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT); -// mNotification.setLatestEventInfo(mContext, "下载完成", -// "文件已下载完毕", contentIntent); - serviceIsDestroy = true; - stopSelf();// 停掉服务自身 - } - mNotificationManager.notify(NOTIFY_ID, mNotification); - break; - } - } - }; - - @Override - public IBinder onBind(Intent intent) { - downloadUrl = intent.getStringExtra(BUNDLE_KEY_DOWNLOAD_URL); - saveFileName = saveFileName + getSaveFileName(downloadUrl); - mTitle = String.format(mTitle, intent.getStringExtra(BUNDLE_KEY_TITLE)); - return binder; - } - - private String getSaveFileName(String downloadUrl) { - if (downloadUrl == null || StringUtils.isEmpty(downloadUrl)) { - return ""; - } - return downloadUrl.substring(downloadUrl.lastIndexOf("/")); - } - - @Override - public void onCreate() { - super.onCreate(); - binder = new DownloadBinder(); - mNotificationManager = (NotificationManager) getSystemService(android.content.Context.NOTIFICATION_SERVICE); - stopForeground(true);// 这个不确定是否有作用 - } - - private void startDownload() { - canceled = false; - downloadApk(); - } - - /** - * 创建通知 - */ - private void setUpNotification() { - int icon = R.drawable.ic_notification; - CharSequence tickerText = "准备下载"; - long when = System.currentTimeMillis(); - mNotification = new Notification(icon, tickerText, when); - ; - // 放置在"正在运行"栏目中 - mNotification.flags = Notification.FLAG_ONGOING_EVENT; - - RemoteViews contentView = new RemoteViews(getPackageName(), - R.layout.download_notification_show); - contentView.setTextViewText(R.id.tv_download_state, mTitle); - // 指定个性化视图 - mNotification.contentView = contentView; - - Intent intent = new Intent(this, MainActivity.class); - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, - intent, PendingIntent.FLAG_UPDATE_CURRENT); - - // 指定内容意图 - mNotification.contentIntent = contentIntent; - mNotificationManager.notify(NOTIFY_ID, mNotification); - } - - private void downloadApk() { - downLoadThread = new Thread(mdownApkRunnable); - downLoadThread.start(); - } - - /** - * 安装apk - */ - private void installApk() { - File apkfile = new File(saveFileName); - if (!apkfile.exists()) { - return; - } - TDevice.installAPK(mContext, apkfile); - } - - private Runnable mdownApkRunnable = new Runnable() { - @Override - public void run() { - File file = new File(AppConfig.DEFAULT_SAVE_FILE_PATH); - if (!file.exists()) { - file.mkdirs(); - } - String apkFile = saveFileName; - File saveFile = new File(apkFile); - try { - downloadUpdateFile(downloadUrl, saveFile); - } catch (Exception e) { - e.printStackTrace(); - } - } - }; - - public long downloadUpdateFile(String downloadUrl, File saveFile) - throws Exception { - int downloadCount = 0; - int currentSize = 0; - long totalSize = 0; - int updateTotalSize = 0; - - HttpURLConnection httpConnection = null; - InputStream is = null; - FileOutputStream fos = null; - - try { - URL url = new URL(downloadUrl); - httpConnection = (HttpURLConnection) url.openConnection(); - httpConnection - .setRequestProperty("User-Agent", "PacificHttpClient"); - if (currentSize > 0) { - httpConnection.setRequestProperty("RANGE", "bytes=" - + currentSize + "-"); - } - httpConnection.setConnectTimeout(10000); - httpConnection.setReadTimeout(20000); - updateTotalSize = httpConnection.getContentLength(); - if (httpConnection.getResponseCode() == 404) { - throw new Exception("fail!"); - } - is = httpConnection.getInputStream(); - fos = new FileOutputStream(saveFile, false); - byte buffer[] = new byte[1024]; - int readsize = 0; - while ((readsize = is.read(buffer)) > 0) { - fos.write(buffer, 0, readsize); - totalSize += readsize; - // 为了防止频繁的通知导致应用吃紧,百分比增加10才通知一次 - if ((downloadCount == 0) - || (int) (totalSize * 100 / updateTotalSize) - 10 >= downloadCount) { - downloadCount += 10; - // 更新进度 - Message msg = mHandler.obtainMessage(); - msg.what = 1; - msg.arg1 = downloadCount; - mHandler.sendMessage(msg); - if (callback != null) - callback.OnBackResult(progress); - } - } - - // 下载完成通知安装 - mHandler.sendEmptyMessage(0); - // 下载完了,cancelled也要设置 - canceled = true; - - } finally { - if (httpConnection != null) { - httpConnection.disconnect(); - } - if (is != null) { - is.close(); - } - if (fos != null) { - fos.close(); - } - } - return totalSize; - } - - public class DownloadBinder extends Binder { - public void start() { - if (downLoadThread == null || !downLoadThread.isAlive()) { - progress = 0; - setUpNotification(); - new Thread() { - public void run() { - // 下载 - startDownload(); - }; - }.start(); - } - } - - public void cancel() { - canceled = true; - } - - public int getProgress() { - return progress; - } - - public boolean isCanceled() { - return canceled; - } - - public boolean serviceIsDestroy() { - return serviceIsDestroy; - } - - public void cancelNotification() { - mHandler.sendEmptyMessage(2); - } - - public void addCallback(ICallbackResult callback) { - DownloadService.this.callback = callback; - } - } -} diff --git a/app/src/main/java/net/oschina/app/service/INoticeService.aidl b/app/src/main/java/net/oschina/app/service/INoticeService.aidl deleted file mode 100644 index fcd7d7b32e3f0f2b404384036f62b9e36eb2795d..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/service/INoticeService.aidl +++ /dev/null @@ -1,8 +0,0 @@ -package net.oschina.app.service; - -interface INoticeService -{ - void scheduleNotice(); - void requestNotice(); - void clearNotice(int uid,int type); -} \ No newline at end of file diff --git a/app/src/main/java/net/oschina/app/service/NoticeService.java b/app/src/main/java/net/oschina/app/service/NoticeService.java deleted file mode 100644 index 3a5b08dab6bf017240505e2a529cac39e195ca15..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/service/NoticeService.java +++ /dev/null @@ -1,308 +0,0 @@ -package net.oschina.app.service; - -import java.io.ByteArrayInputStream; -import java.lang.ref.WeakReference; - -import net.oschina.app.AppConfig; -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.bean.Constants; -import net.oschina.app.bean.Notice; -import net.oschina.app.bean.NoticeDetail; -import net.oschina.app.bean.Result; -import net.oschina.app.bean.ResultBean; -import net.oschina.app.broadcast.AlarmReceiver; -import net.oschina.app.ui.MainActivity; -import net.oschina.app.util.UIHelper; -import net.oschina.app.util.XmlUtils; - -import cz.msebera.android.httpclient.Header; -import android.app.AlarmManager; -import android.app.Notification; -import android.app.PendingIntent; -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; -import android.net.Uri; -import android.os.IBinder; -import android.os.RemoteException; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.NotificationManagerCompat; - -import com.loopj.android.http.AsyncHttpResponseHandler; - -public class NoticeService extends Service { - public static final String INTENT_ACTION_GET = "net.oschina.app.service.GET_NOTICE"; - public static final String INTENT_ACTION_CLEAR = "net.oschina.app.service.CLEAR_NOTICE"; - public static final String INTENT_ACTION_BROADCAST = "net.oschina.app.service.BROADCAST"; - public static final String INTENT_ACTION_SHUTDOWN = "net.oschina.app.service.SHUTDOWN"; - public static final String INTENT_ACTION_REQUEST = "net.oschina.app.service.REQUEST"; - public static final String BUNDLE_KEY_TPYE = "bundle_key_type"; - - private static final long INTERVAL = 1000 * 120; - private AlarmManager mAlarmMgr; - - private Notice mNotice; - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Constants.INTENT_ACTION_NOTICE.equals(action)) { - Notice notice = (Notice) intent.getSerializableExtra("notice_bean"); - int atmeCount = notice.getAtmeCount();// @我 - int msgCount = notice.getMsgCount();// 私信 - int reviewCount = notice.getReviewCount();// 评论 - int newFansCount = notice.getNewFansCount();// 新粉丝 - int newLikeCount = notice.getNewLikeCount();// 点赞数 - int activeCount = atmeCount + reviewCount + msgCount - + newFansCount + newLikeCount; - if (activeCount == 0) { - NotificationManagerCompat.from(NoticeService.this).cancel( - R.string.you_have_news_messages); - } - } else if (INTENT_ACTION_BROADCAST.equals(action)) { - if (mNotice != null) { - UIHelper.sendBroadCast(NoticeService.this, mNotice); - } - } else if (INTENT_ACTION_SHUTDOWN.equals(action)) { - stopSelf(); - } else if (INTENT_ACTION_REQUEST.equals(action)) { - requestNotice(); - } - } - }; - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - @Override - public void onCreate() { - super.onCreate(); - mAlarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE); - startRequestAlarm(); - requestNotice(); - - IntentFilter filter = new IntentFilter(INTENT_ACTION_BROADCAST); - filter.addAction(Constants.INTENT_ACTION_NOTICE); - filter.addAction(INTENT_ACTION_SHUTDOWN); - filter.addAction(INTENT_ACTION_REQUEST); - registerReceiver(mReceiver, filter); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - return super.onStartCommand(intent, flags, startId); - } - - @Override - public void onDestroy() { - cancelRequestAlarm(); - unregisterReceiver(mReceiver); - super.onDestroy(); - } - - private void startRequestAlarm() { - cancelRequestAlarm(); - // 从1秒后开始,每隔2分钟执行getOperationIntent() - mAlarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + 1000, INTERVAL, - getOperationIntent()); - } - - /** - * 即使启动PendingIntent的原进程结束了的话,PendingIntent本身仍然还存在,可在其他进程( - * PendingIntent被递交到的其他程序)中继续使用. - * 如果我在从系统中提取一个PendingIntent的,而系统中有一个和你描述的PendingIntent对等的PendingInent, - * 那么系统会直接返回和该PendingIntent其实是同一token的PendingIntent, - * 而不是一个新的token和PendingIntent。然而你在从提取PendingIntent时,通过FLAG_CANCEL_CURRENT参数, - * 让这个老PendingIntent的先cancel()掉,这样得到的pendingInten和其token的就是新的了。 - */ - private void cancelRequestAlarm() { - mAlarmMgr.cancel(getOperationIntent()); - } - - /** - * OSC采用轮询方式实现消息推送
    - * 每次被调用都去执行一次{@link #AlarmReceiver}onReceive()方法 - * - * @return - */ - private PendingIntent getOperationIntent() { - Intent intent = new Intent(this, AlarmReceiver.class); - PendingIntent operation = PendingIntent.getBroadcast(this, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - return operation; - } - - private void clearNotice(int uid, int type) { - OSChinaApi.clearNotice(uid, type, mClearNoticeHandler); - } - - private int lastNotifiyCount; - - private void notification(Notice notice) { - int atmeCount = notice.getAtmeCount(); - int msgCount = notice.getMsgCount(); - int reviewCount = notice.getReviewCount(); - int newFansCount = notice.getNewFansCount(); - int newLikeCount = notice.getNewLikeCount(); - - int count = atmeCount + msgCount + reviewCount + newFansCount + newLikeCount; - - if (count == 0) { - lastNotifiyCount = 0; - NotificationManagerCompat.from(this).cancel( - R.string.you_have_news_messages); - return; - } - if (count == lastNotifiyCount) - return; - - lastNotifiyCount = count; - - Resources res = getResources(); - String contentTitle = res.getString(R.string.you_have_news_messages, - count); - String contentText; - StringBuffer sb = new StringBuffer(); - if (atmeCount > 0) { - sb.append(getString(R.string.atme_count, atmeCount)).append(" "); - } - if (msgCount > 0) { - sb.append(getString(R.string.msg_count, msgCount)).append(" "); - } - if (reviewCount > 0) { - sb.append(getString(R.string.review_count, reviewCount)) - .append(" "); - } - if (newFansCount > 0) { - sb.append(getString(R.string.fans_count, newFansCount)); - } - if (newLikeCount > 0) { - sb.append(getString(R.string.like_count, newLikeCount)); - } - contentText = sb.toString(); - - Intent intent = new Intent(this, MainActivity.class); - intent.putExtra("NOTICE", true); - - PendingIntent pi = PendingIntent.getActivity(this, 1000, intent, - PendingIntent.FLAG_CANCEL_CURRENT); - - NotificationCompat.Builder builder = new NotificationCompat.Builder( - this).setTicker(contentTitle).setContentTitle(contentTitle) - .setContentText(contentText).setAutoCancel(true) - .setContentIntent(pi).setSmallIcon(R.drawable.ic_notification); - - if (AppContext.get(AppConfig.KEY_NOTIFICATION_SOUND, true)) { - builder.setSound(Uri.parse("android.resource://" - + AppContext.getInstance().getPackageName() + "/" - + R.raw.notificationsound)); - } - if (AppContext.get(AppConfig.KEY_NOTIFICATION_VIBRATION, true)) { - long[] vibrate = { 0, 10, 20, 30 }; - builder.setVibrate(vibrate); - } - - Notification notification = builder.build(); - - NotificationManagerCompat.from(this).notify( - R.string.you_have_news_messages, notification); - } - - private final AsyncHttpResponseHandler mGetNoticeHandler = new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - try { - Notice notice = XmlUtils.toBean(NoticeDetail.class, - arg2).getNotice(); - if (notice != null) { - UIHelper.sendBroadCast(NoticeService.this, notice); - if (AppContext.get(AppConfig.KEY_NOTIFICATION_ACCEPT, true)) { - notification(notice); - } - mNotice = notice; - } else { -// ResultBean resultBean = XmlUtils.toBean(ResultBean.class, arg2); -// if (resultBean != null && resultBean.getResult() != null) { -// AppContext appContext = AppContext.getInstance(); -// if (appContext != null) { -// appContext.Logout(); -// } -// } - } - } catch (Exception e) { - e.printStackTrace(); - onFailure(arg0, arg1, arg2, e); - } - }; - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) { - arg3.printStackTrace(); - } - }; - - private final AsyncHttpResponseHandler mClearNoticeHandler = new AsyncHttpResponseHandler() { - - @Override - public void onSuccess(int arg0, Header[] arg1, byte[] arg2) { - try { - ResultBean rsb = XmlUtils.toBean(ResultBean.class, - new ByteArrayInputStream(arg2)); - Result res = rsb.getResult(); - if (res.OK() && rsb.getNotice() != null) { - mNotice = rsb.getNotice(); - UIHelper.sendBroadCast(NoticeService.this, rsb.getNotice()); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - @Override - public void onFailure(int arg0, Header[] arg1, byte[] arg2, - Throwable arg3) {} - }; - - /** - * 请求是否有新通知 - */ - private void requestNotice() { - OSChinaApi.getNotices(mGetNoticeHandler); - } - - private static class ServiceStub extends INoticeService.Stub { - WeakReference mService; - - ServiceStub(NoticeService service) { - mService = new WeakReference(service); - } - - @Override - public void clearNotice(int uid, int type) throws RemoteException { - mService.get().clearNotice(uid, type); - } - - @Override - public void scheduleNotice() throws RemoteException { - mService.get().startRequestAlarm(); - } - - @Override - public void requestNotice() throws RemoteException { - mService.get().requestNotice(); - } - } - - private final IBinder mBinder = new ServiceStub(this); -} diff --git a/app/src/main/java/net/oschina/app/service/NoticeUtils.java b/app/src/main/java/net/oschina/app/service/NoticeUtils.java deleted file mode 100644 index 9d836947986baae701252c91573ecd82f08b0b18..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/service/NoticeUtils.java +++ /dev/null @@ -1,116 +0,0 @@ -package net.oschina.app.service; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.RemoteException; -import android.util.Log; - -import net.oschina.app.AppConfig; -import net.oschina.app.AppContext; -import net.oschina.app.util.TLog; - -import java.util.HashMap; - -public class NoticeUtils { - - public static INoticeService sService = null; - private static HashMap sConnectionMap = new HashMap(); - - public static boolean bindToService(Context context) { - return bindToService(context, null); - } - - public static boolean bindToService(Context context, - ServiceConnection callback) { - context.startService(new Intent(context, NoticeService.class)); - ServiceBinder sb = new ServiceBinder(callback); - sConnectionMap.put(context, sb); - return context.bindService( - (new Intent()).setClass(context, NoticeService.class), sb, 0); - } - - public static void unbindFromService(Context context) { - ServiceBinder sb = sConnectionMap.remove(context); - if (sb == null) { - Log.e("MusicUtils", "Trying to unbind for unknown Context"); - return; - } - context.unbindService(sb); - if (sConnectionMap.isEmpty()) { - // presumably there is nobody interested in the service at this - // point, - // so don't hang on to the ServiceConnection - sService = null; - } - } - - public static void clearNotice(int type) { - if (sService != null) { - try { - sService.clearNotice(AppContext.getInstance().getLoginUid(), - type); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - } - - public static void requestNotice(Context context) { - if (sService != null) { - try { - TLog.log("requestNotice..."); - sService.requestNotice(); - } catch (RemoteException e) { - e.printStackTrace(); - } - } else { - context.sendBroadcast(new Intent( - NoticeService.INTENT_ACTION_REQUEST)); - TLog.log("requestNotice,service is null"); - } - } - - public static void scheduleNotice() { - if (sService != null) { - try { - sService.scheduleNotice(); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - } - - private static class ServiceBinder implements ServiceConnection { - ServiceConnection mCallback; - - ServiceBinder(ServiceConnection callback) { - mCallback = callback; - } - - @Override - public void onServiceConnected(ComponentName className, - android.os.IBinder service) { - sService = INoticeService.Stub.asInterface(service); - if (mCallback != null) { - mCallback.onServiceConnected(className, service); - } - } - - @Override - public void onServiceDisconnected(ComponentName className) { - if (mCallback != null) { - mCallback.onServiceDisconnected(className); - } - sService = null; - } - } - - public static void tryToShutDown(Context context) { - if (AppContext.get(AppConfig.KEY_NOTIFICATION_DISABLE_WHEN_EXIT, true)) { - context.sendBroadcast(new Intent( - NoticeService.INTENT_ACTION_SHUTDOWN)); - } - } -} diff --git a/app/src/main/java/net/oschina/app/service/PublicCommentTask.java b/app/src/main/java/net/oschina/app/service/PublicCommentTask.java deleted file mode 100644 index e5eb65c68f212be4fea1af7aa13953fbd7631cc0..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/service/PublicCommentTask.java +++ /dev/null @@ -1,90 +0,0 @@ -package net.oschina.app.service; - -import android.os.Parcel; -import android.os.Parcelable; - -public class PublicCommentTask implements Parcelable { - private int catalog; - private int id; - private int uid; - private String content; - private int isPostToMyZone; - - public PublicCommentTask() { - } - - public PublicCommentTask(Parcel source) { - catalog = source.readInt(); - id = source.readInt(); - uid = source.readInt(); - content = source.readString(); - isPostToMyZone = source.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(catalog); - dest.writeInt(id); - dest.writeInt(uid); - dest.writeString(content); - dest.writeInt(isPostToMyZone); - } - - public int getCatalog() { - return catalog; - } - - public void setCatalog(int catalog) { - this.catalog = catalog; - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public int getUid() { - return uid; - } - - public void setUid(int uid) { - this.uid = uid; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public int getIsPostToMyZone() { - return isPostToMyZone; - } - - public void setIsPostToMyZone(int isPostToMyZone) { - this.isPostToMyZone = isPostToMyZone; - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator CREATOR = new Creator() { - - @Override - public PublicCommentTask[] newArray(int size) { - return new PublicCommentTask[size]; - } - - @Override - public PublicCommentTask createFromParcel(Parcel source) { - return new PublicCommentTask(source); - } - }; -} diff --git a/app/src/main/java/net/oschina/app/service/ServerTaskService.java b/app/src/main/java/net/oschina/app/service/ServerTaskService.java deleted file mode 100644 index bbb618a3883df4e2fec8029f699f9a55a1befc7f..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/service/ServerTaskService.java +++ /dev/null @@ -1,306 +0,0 @@ -package net.oschina.app.service; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import net.oschina.app.AppContext; -import net.oschina.app.R; -import net.oschina.app.api.OperationResponseHandler; -import net.oschina.app.api.remote.OSChinaApi; -import net.oschina.app.base.ListBaseAdapter; -import net.oschina.app.bean.Comment; -import net.oschina.app.bean.Result; -import net.oschina.app.bean.ResultBean; -import net.oschina.app.bean.Tweet; -import net.oschina.app.util.XmlUtils; -import android.app.IntentService; -import android.app.Notification; -import android.app.PendingIntent; -import android.content.Intent; -import android.os.Handler; -import android.os.Looper; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.NotificationManagerCompat; - -public class ServerTaskService extends IntentService { - private static final String SERVICE_NAME = "ServerTaskService"; - public static final String ACTION_PUB_BLOG_COMMENT = "net.oschina.app.ACTION_PUB_BLOG_COMMENT"; - public static final String ACTION_PUB_COMMENT = "net.oschina.app.ACTION_PUB_COMMENT"; - public static final String ACTION_PUB_POST = "net.oschina.app.ACTION_PUB_POST"; - public static final String ACTION_PUB_TWEET = "net.oschina.app.ACTION_PUB_TWEET"; - public static final String ACTION_PUB_SOFTWARE_TWEET = "net.oschina.app.ACTION_PUB_SOFTWARE_TWEET"; - - public static final String KEY_ADAPTER = "adapter"; - - public static final String BUNDLE_PUB_COMMENT_TASK = "BUNDLE_PUB_COMMENT_TASK"; - public static final String BUNDLE_PUB_POST_TASK = "BUNDLE_PUB_POST_TASK"; - public static final String BUNDLE_PUB_TWEET_TASK = "BUNDLE_PUB_TWEET_TASK"; - public static final String BUNDLE_PUB_SOFTWARE_TWEET_TASK = "BUNDLE_PUB_SOFTWARE_TWEET_TASK"; - public static final String KEY_SOFTID = "soft_id"; - - private static final String KEY_COMMENT = "comment_"; - private static final String KEY_TWEET = "tweet_"; - private static final String KEY_SOFTWARE_TWEET = "software_tweet_"; - private static final String KEY_POST = "post_"; - - public static List penddingTasks = new ArrayList(); - - class PublicCommentResponseHandler extends OperationResponseHandler { - - public PublicCommentResponseHandler(Looper looper, Object... args) { - super(looper, args); - } - - @Override - public void onSuccess(int code, ByteArrayInputStream is, Object[] args) - throws Exception { - PublicCommentTask task = (PublicCommentTask) args[0]; - final int id = task.getId() * task.getUid(); - ResultBean resB = XmlUtils.toBean(ResultBean.class, is); - Result res = resB.getResult(); - if (res.OK()) { - final Comment comment = resB.getComment(); - // UIHelper.sendBroadCastCommentChanged(ServerTaskService.this, - // isBlog, task.getId(), task.getCatalog(), - // Comment.OPT_ADD, comment); - notifySimpleNotifycation(id, - getString(R.string.comment_publish_success), - getString(R.string.comment_blog), - getString(R.string.comment_publish_success), false, - true); - removePenddingTask(KEY_COMMENT + id); - } else { - onFailure(100, res.getErrorMessage(), args); - } - } - - @Override - public void onFailure(int code, String errorMessage, Object[] args) { - PublicCommentTask task = (PublicCommentTask) args[0]; - int id = task.getId() * task.getUid(); - notifySimpleNotifycation(id, - getString(R.string.comment_publish_faile), - getString(R.string.comment_blog), - code == 100 ? errorMessage - : getString(R.string.comment_publish_faile), false, - true); - removePenddingTask(KEY_COMMENT + id); - } - - @Override - public void onFinish() { - tryToStopServie(); - } - } - - class PublicTweetResponseHandler extends OperationResponseHandler { - - String key = null; - - public PublicTweetResponseHandler(Looper looper, Object... args) { - super(looper, args); - key = (String) args[1]; - } - - @Override - public void onSuccess(int code, ByteArrayInputStream is, Object[] args) - throws Exception { - Tweet tweet = (Tweet) args[0]; - final int id = tweet.getId(); - Result res = XmlUtils.toBean(ResultBean.class, is).getResult(); - if (res.OK()) { - notifySimpleNotifycation(id, - getString(R.string.tweet_publish_success), - getString(R.string.tweet_public), - getString(R.string.tweet_publish_success), false, true); - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - cancellNotification(id); - } - }, 3000); - removePenddingTask(key + id); - if (tweet.getImageFilePath() != null) { - File imgFile = new File(tweet.getImageFilePath()); - if (imgFile.exists()) { - imgFile.delete(); - } - } - } else { - onFailure(100, res.getErrorMessage(), args); - } - } - - @Override - public void onFailure(int code, String errorMessage, Object[] args) { - Tweet tweet = (Tweet) args[0]; - int id = tweet.getId(); - notifySimpleNotifycation(id, - getString(R.string.tweet_publish_faile), - getString(R.string.tweet_public), - code == 100 ? errorMessage - : getString(R.string.tweet_publish_faile), false, - true); - removePenddingTask(key + id); - } - - @Override - public void onFinish() { - tryToStopServie(); - } - } - - public ServerTaskService() { - this(SERVICE_NAME); - } - - private synchronized void tryToStopServie() { - if (penddingTasks == null || penddingTasks.size() == 0) { - stopSelf(); - } - } - - private synchronized void addPenddingTask(String key) { - penddingTasks.add(key); - } - - private synchronized void removePenddingTask(String key) { - penddingTasks.remove(key); - } - - public ServerTaskService(String name) { - super(name); - } - - @Override - public void onCreate() { - super.onCreate(); - - } - - @Override - protected void onHandleIntent(Intent intent) { - String action = intent.getAction(); - - if (ACTION_PUB_BLOG_COMMENT.equals(action)) { - PublicCommentTask task = intent - .getParcelableExtra(BUNDLE_PUB_COMMENT_TASK); - if (task != null) { - publicBlogComment(task); - } - } else if (ACTION_PUB_COMMENT.equals(action)) { - PublicCommentTask task = intent - .getParcelableExtra(BUNDLE_PUB_COMMENT_TASK); - if (task != null) { - publicComment(task); - } - } else if (ACTION_PUB_POST.equals(action)) { - // Post post = intent.getParcelableExtra(BUNDLE_PUBLIC_POST_TASK); - // if (post != null) { - // publicPost(post); - // } - } else if (ACTION_PUB_TWEET.equals(action)) { - Tweet tweet = intent.getParcelableExtra(BUNDLE_PUB_TWEET_TASK); - if (tweet != null) { - pubTweet(tweet); - } - } else if (ACTION_PUB_SOFTWARE_TWEET.equals(action)) { - Tweet tweet = intent - .getParcelableExtra(BUNDLE_PUB_SOFTWARE_TWEET_TASK); - int softid = intent.getIntExtra(KEY_SOFTID, -1); - if (tweet != null && softid != -1) { - pubSoftWareTweet(tweet, softid); - } - } - } - - private void publicBlogComment(final PublicCommentTask task) { - int id = task.getId() * task.getUid(); - addPenddingTask(KEY_COMMENT + id); - - notifySimpleNotifycation(id, getString(R.string.comment_publishing), - getString(R.string.comment_blog), - getString(R.string.comment_publishing), true, false); - - OSChinaApi.publicBlogComment(task.getId(), task.getUid(), task - .getContent(), new PublicCommentResponseHandler( - getMainLooper(), task, true)); - } - - private void publicComment(final PublicCommentTask task) { - int id = task.getId() * task.getUid(); - addPenddingTask(KEY_COMMENT + id); - - notifySimpleNotifycation(id, getString(R.string.comment_publishing), - getString(R.string.comment_blog), - getString(R.string.comment_publishing), true, false); - - OSChinaApi.publicComment(task.getCatalog(), task.getId(), - task.getUid(), task.getContent(), task.getIsPostToMyZone(), - new PublicCommentResponseHandler(getMainLooper(), task, false)); - } - - // private void publicPost(Post post) { - // post.setId((int) System.currentTimeMillis()); - // int id = post.getId(); - // addPenddingTask(KEY_POST + id); - // notifySimpleNotifycation(id, getString(R.string.post_publishing), - // getString(R.string.post_public), - // getString(R.string.post_publishing), true, false); - // OSChinaApi.publicPost(post, new - // PublicPostResponseHandler(getMainLooper(), - // post)); - // } - // - private void pubTweet(final Tweet tweet) { - tweet.setId((int) System.currentTimeMillis()); - int id = tweet.getId(); - addPenddingTask(KEY_TWEET + id); - notifySimpleNotifycation(id, getString(R.string.tweet_publishing), - getString(R.string.tweet_public), - getString(R.string.tweet_publishing), true, false); - OSChinaApi.pubTweet(tweet, new PublicTweetResponseHandler( - getMainLooper(), tweet, KEY_TWEET)); - } - - private void pubSoftWareTweet(final Tweet tweet, int softid) { - tweet.setId((int) System.currentTimeMillis()); - int id = tweet.getId(); - addPenddingTask(KEY_SOFTWARE_TWEET + id); - notifySimpleNotifycation(id, getString(R.string.tweet_publishing), - getString(R.string.tweet_public), - getString(R.string.tweet_publishing), true, false); - OSChinaApi.pubSoftWareTweet(tweet, softid, - new PublicTweetResponseHandler(getMainLooper(), tweet, - KEY_SOFTWARE_TWEET)); - } - - private void notifySimpleNotifycation(int id, String ticker, String title, - String content, boolean ongoing, boolean autoCancel) { - NotificationCompat.Builder builder = new NotificationCompat.Builder( - this) - .setTicker(ticker) - .setContentTitle(title) - .setContentText(content) - .setAutoCancel(true) - .setOngoing(false) - .setOnlyAlertOnce(true) - .setContentIntent( - PendingIntent.getActivity(this, 0, new Intent(), 0)) - .setSmallIcon(R.drawable.ic_notification); - - // if (AppContext.isNotificationSoundEnable()) { - // builder.setDefaults(Notification.DEFAULT_SOUND); - // } - - Notification notification = builder.build(); - - NotificationManagerCompat.from(this).notify(id, notification); - } - - private void cancellNotification(int id) { - NotificationManagerCompat.from(this).cancel(id); - } -} diff --git a/app/src/main/java/net/oschina/app/service/ServerTaskUtils.java b/app/src/main/java/net/oschina/app/service/ServerTaskUtils.java deleted file mode 100644 index 4ae0f3bbcec62345d0e3a72fc77f9d5738b7ed67..0000000000000000000000000000000000000000 --- a/app/src/main/java/net/oschina/app/service/ServerTaskUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.oschina.app.service; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; - -import net.oschina.app.AppContext; -import net.oschina.app.bean.Tweet; - -public class ServerTaskUtils { - - public static void pubTweet(Context context, Tweet tweet) { - Intent intent = new Intent(ServerTaskService.ACTION_PUB_TWEET); - Bundle bundle = new Bundle(); - bundle.putParcelable(ServerTaskService.BUNDLE_PUB_TWEET_TASK, tweet); - intent.putExtras(bundle); - intent.setPackage(AppContext.getInstance().getPackageName()); - context.startService(intent); - } - - public static void pubSoftWareTweet(Context context, Tweet tweet, int softid) { - Intent intent = new Intent(ServerTaskService.ACTION_PUB_SOFTWARE_TWEET); - Bundle bundle = new Bundle(); - bundle.putParcelable(ServerTaskService.BUNDLE_PUB_SOFTWARE_TWEET_TASK, - tweet); - bundle.putInt(ServerTaskService.KEY_SOFTID, softid); - intent.putExtras(bundle); - intent.setPackage(AppContext.getInstance().getPackageName()); - context.startService(intent); - } -} diff --git a/app/src/main/java/net/oschina/app/team/adapter/NotebookAdapter.java b/app/src/main/java/net/oschina/app/team/adapter/NotebookAdapter.java index 95ca76af42a91d47829a509ae6d66fd05d9580c3..9ff9a80a32955aadaf72db2fbdbeccfc230e6f74 100644 --- a/app/src/main/java/net/oschina/app/team/adapter/NotebookAdapter.java +++ b/app/src/main/java/net/oschina/app/team/adapter/NotebookAdapter.java @@ -1,16 +1,5 @@ package net.oschina.app.team.adapter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import net.oschina.app.R; -import net.oschina.app.bean.NotebookData; -import net.oschina.app.team.fragment.NoteEditFragment; -import net.oschina.app.widget.KJDragGridView.DragGridBaseAdapter; - -import org.kymjs.kjframe.utils.DensityUtils; - import android.app.Activity; import android.text.Html; import android.view.View; @@ -21,11 +10,20 @@ import android.widget.RelativeLayout; import android.widget.RelativeLayout.LayoutParams; import android.widget.TextView; +import net.oschina.app.R; +import net.oschina.app.bean.NotebookData; +import net.oschina.app.team.fragment.NoteEditFragment; +import net.oschina.app.util.TDevice; +import net.oschina.app.widget.KJDragGridView.DragGridBaseAdapter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * 便签列表适配器 - * + * * @author kymjs (https://github.com/kymjs) - * */ public class NotebookAdapter extends BaseAdapter implements DragGridBaseAdapter { private List datas; @@ -40,7 +38,7 @@ public class NotebookAdapter extends BaseAdapter implements DragGridBaseAdapter Collections.sort(datas); this.datas = datas; this.aty = aty; - width = DensityUtils.getScreenW(aty) / 2; + width = (int) (TDevice.getScreenWidth() / 2); height = (int) aty.getResources().getDimension(R.dimen.space_35); } @@ -74,7 +72,7 @@ public class NotebookAdapter extends BaseAdapter implements DragGridBaseAdapter /** * 数据是否发生了改变 - * + * * @return */ public boolean getDataChange() { diff --git a/app/src/main/java/net/oschina/app/team/adapter/TeamActiveAdapter.java b/app/src/main/java/net/oschina/app/team/adapter/TeamActiveAdapter.java index 70a89a0c30d6df8e8b1da93958a9d8959a3c4f6e..b7c8440118fa6c0f56117f9dd89fad866115e9f8 100644 --- a/app/src/main/java/net/oschina/app/team/adapter/TeamActiveAdapter.java +++ b/app/src/main/java/net/oschina/app/team/adapter/TeamActiveAdapter.java @@ -1,24 +1,22 @@ package net.oschina.app.team.adapter; import android.content.Context; -import android.graphics.Bitmap; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; + import net.oschina.app.R; import net.oschina.app.base.ListBaseAdapter; import net.oschina.app.team.bean.TeamActive; -import net.oschina.app.ui.ImagePreviewActivity; -import net.oschina.app.util.BitmapHelper; +import net.oschina.app.ui.OSCPhotosActivity; import net.oschina.app.util.StringUtils; import net.oschina.app.widget.AvatarView; import net.oschina.app.widget.TweetTextView; -import org.kymjs.kjframe.Core; -import org.kymjs.kjframe.bitmap.BitmapCallBack; - /** * Team动态界面ListView适配器 (kymjs123@gmail.com) * @@ -26,9 +24,11 @@ import org.kymjs.kjframe.bitmap.BitmapCallBack; */ public class TeamActiveAdapter extends ListBaseAdapter { private final Context context; + private RequestManager mManager; public TeamActiveAdapter(Context cxt) { this.context = cxt; + mManager = Glide.with(context); } static class ViewHolder { @@ -67,16 +67,17 @@ public class TeamActiveAdapter extends ListBaseAdapter { } else { holder = (ViewHolder) v.getTag(); } - holder.img_head.setAvatarUrl(data.getAuthor().getPortrait()); + //holder.img_head.setAvatarUrl(data.getAuthor().getPortrait()); + mManager.load(data.getAuthor().getPortrait()).asBitmap().into(holder.img_head); holder.img_head.setUserInfo(data.getAuthor().getId(), data.getAuthor() .getName()); holder.tv_name.setText(data.getAuthor().getName()); setContent(holder.tv_content, stripTags(data.getBody().getTitle())); - String date = StringUtils.friendly_time2(data.getCreateTime()); + String date = StringUtils.formatDayWeek(data.getCreateTime()); String preDate = ""; if (position > 0) { - preDate = StringUtils.friendly_time2(mDatas.get(position - 1) + preDate = StringUtils.formatDayWeek(mDatas.get(position - 1) .getCreateTime()); } if (preDate.equals(date)) { @@ -87,7 +88,7 @@ public class TeamActiveAdapter extends ListBaseAdapter { } holder.tv_content.setMaxLines(3); - holder.tv_date.setText(StringUtils.friendly_time(data.getCreateTime())); + holder.tv_date.setText(StringUtils.formatSomeAgo(data.getCreateTime())); holder.tv_commit.setText(data.getReply()); String imgPath = data.getBody().getImage(); @@ -128,23 +129,14 @@ public class TeamActiveAdapter extends ListBaseAdapter { private void setTweetImage(final ImageView pic, final String url) { pic.setVisibility(View.VISIBLE); - Core.getKJBitmap().display(pic, url, R.drawable.pic_bg, 0, 0, new BitmapCallBack() { - @Override - public void onSuccess(Bitmap bitmap) { - super.onSuccess(bitmap); - if (bitmap != null) { - bitmap = BitmapHelper.scaleWithXY(bitmap, - 360 / bitmap.getHeight()); - pic.setImageBitmap(bitmap); - } - } - }); + Glide.with(context).load(url) + .placeholder(R.drawable.pic_bg) + .into(pic); pic.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - ImagePreviewActivity.showImagePrivew(context, 0, - new String[]{url}); + OSCPhotosActivity.showImagePreview(context, url); } }); } diff --git a/app/src/main/java/net/oschina/app/team/adapter/TeamDiaryDetailAdapter.java b/app/src/main/java/net/oschina/app/team/adapter/TeamDiaryDetailAdapter.java index 61c0c5af6ac7eb22a70f8199671596ecf5776f35..745b0a258376c81fecdf66b7d935518d3c4fff9d 100644 --- a/app/src/main/java/net/oschina/app/team/adapter/TeamDiaryDetailAdapter.java +++ b/app/src/main/java/net/oschina/app/team/adapter/TeamDiaryDetailAdapter.java @@ -136,10 +136,10 @@ public class TeamDiaryDetailAdapter extends BaseAdapter { /** * 移除字符串中的Html标签 - * - * @author kymjs (https://github.com/kymjs) + * * @param pHTMLString * @return + * @author kymjs (https://github.com/kymjs) */ public static Spanned stripTags(final String pHTMLString) { // String str = pHTMLString.replaceAll("\\<.*?>", ""); diff --git a/app/src/main/java/net/oschina/app/team/adapter/TeamDiaryListAdapter.java b/app/src/main/java/net/oschina/app/team/adapter/TeamDiaryListAdapter.java index 774f52f69ea45f00371f8b34ad3c248cfba0d83e..9b51e28b3438ea9232eac199dd452b67c178d15b 100644 --- a/app/src/main/java/net/oschina/app/team/adapter/TeamDiaryListAdapter.java +++ b/app/src/main/java/net/oschina/app/team/adapter/TeamDiaryListAdapter.java @@ -17,9 +17,8 @@ import java.util.List; /** * 周报的ListView适配器 - * + * * @author kymjs (http://www.kymjs.com) - * */ public class TeamDiaryListAdapter extends BaseAdapter { private final Context cxt; @@ -74,7 +73,7 @@ public class TeamDiaryListAdapter extends BaseAdapter { } holder.iv_face.setAvatarUrl(data.getAuthor().getPortrait()); holder.tv_author.setText(data.getAuthor().getName()); - holder.tv_date.setText(StringUtils.friendly_time(data.getCreateTime())); + holder.tv_date.setText(StringUtils.formatSomeAgo(data.getCreateTime())); holder.tv_count.setText(data.getReply() + ""); holder.tv_title.setText(Html.fromHtml(data.getTitle()).toString() .trim()); diff --git a/app/src/main/java/net/oschina/app/team/adapter/TeamDiaryPagerAdapter.java b/app/src/main/java/net/oschina/app/team/adapter/TeamDiaryPagerAdapter.java index 453769086077c7e7d412daed7e928d4637dae5f0..686b0da28f8cb133e3cc0a981c1ffb5c752e98eb 100644 --- a/app/src/main/java/net/oschina/app/team/adapter/TeamDiaryPagerAdapter.java +++ b/app/src/main/java/net/oschina/app/team/adapter/TeamDiaryPagerAdapter.java @@ -1,15 +1,16 @@ package net.oschina.app.team.adapter; -import net.oschina.app.util.StringUtils; -import net.oschina.app.widget.DiaryPageContentView; import android.content.Context; import android.support.v4.view.PagerAdapter; import android.view.View; import android.view.ViewGroup; +import net.oschina.app.util.StringUtils; +import net.oschina.app.widget.DiaryPageContentView; + /** * 周报ViewPager适配器 - * + * * @author kymjs (http://www.kymjs.com) */ public class TeamDiaryPagerAdapter extends PagerAdapter { diff --git a/app/src/main/java/net/oschina/app/team/adapter/TeamDiscussAdapter.java b/app/src/main/java/net/oschina/app/team/adapter/TeamDiscussAdapter.java old mode 100755 new mode 100644 index 31fff0a4ac7d4f7873011a18e2f5e89bae32da03..c19ce52179bb1ec0c529b4ba090dca48022c365e --- a/app/src/main/java/net/oschina/app/team/adapter/TeamDiscussAdapter.java +++ b/app/src/main/java/net/oschina/app/team/adapter/TeamDiscussAdapter.java @@ -11,8 +11,8 @@ import net.oschina.app.util.HTMLUtil; import net.oschina.app.util.StringUtils; import net.oschina.app.widget.AvatarView; +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; /** * team 讨论区帖子 @@ -24,24 +24,24 @@ public class TeamDiscussAdapter extends ListBaseAdapter { static class ViewHolder { - @InjectView(R.id.tv_title) + @Bind(R.id.tv_title) TextView title; - @InjectView(R.id.tv_description) + @Bind(R.id.tv_description) TextView description; - @InjectView(R.id.tv_author) + @Bind(R.id.tv_author) TextView author; - @InjectView(R.id.tv_date) + @Bind(R.id.tv_date) TextView time; - @InjectView(R.id.tv_count) + @Bind(R.id.tv_count) TextView comment_count; - @InjectView(R.id.tv_vote_up) + @Bind(R.id.tv_vote_up) TextView vote_up; - @InjectView(R.id.iv_face) + @Bind(R.id.iv_face) public AvatarView face; public ViewHolder(View view) { - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); } } @@ -65,12 +65,10 @@ public class TeamDiscussAdapter extends ListBaseAdapter { vh.title.setText(item.getTitle()); String body = item.getBody().trim(); vh.description.setVisibility(View.GONE); - if (null != body || !StringUtils.isEmpty(body)) { - vh.description.setVisibility(View.VISIBLE); - vh.description.setText(HTMLUtil.replaceTag(item.getBody()).trim()); - } + vh.description.setVisibility(View.VISIBLE); + vh.description.setText(HTMLUtil.replaceTag(body)); vh.author.setText(item.getAuthor().getName()); - vh.time.setText(StringUtils.friendly_time(item.getCreateTime())); + vh.time.setText(StringUtils.formatSomeAgo(item.getCreateTime())); vh.vote_up.setText(item.getVoteUp() + ""); vh.comment_count.setText(item.getAnswerCount() + ""); return convertView; diff --git a/app/src/main/java/net/oschina/app/team/adapter/TeamIssueAdapter.java b/app/src/main/java/net/oschina/app/team/adapter/TeamIssueAdapter.java index 265f6a650465667038d842886feb1b26b854f8f2..87c0f1da15bf88a01a051a0b77f96e68f806c1b2 100644 --- a/app/src/main/java/net/oschina/app/team/adapter/TeamIssueAdapter.java +++ b/app/src/main/java/net/oschina/app/team/adapter/TeamIssueAdapter.java @@ -17,15 +17,14 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; /** * 任务列表适配器 * * @author FireAnt(http://my.oschina.net/LittleDY) * @version 创建时间:2015年1月14日 下午5:28:51 - * */ public class TeamIssueAdapter extends ListBaseAdapter { @@ -47,10 +46,10 @@ public class TeamIssueAdapter extends ListBaseAdapter { vh.title.setText(item.getTitle()); - String date = StringUtils.friendly_time2(item.getCreateTime()); + String date = StringUtils.formatDayWeek(item.getCreateTime()); String preDate = ""; if (position > 0) { - preDate = StringUtils.friendly_time2(mDatas.get(position - 1) + preDate = StringUtils.formatDayWeek(mDatas.get(position - 1) .getCreateTime()); } if (preDate.equals(date)) { @@ -75,7 +74,7 @@ public class TeamIssueAdapter extends ListBaseAdapter { vh.touser.setText(item.getToUser().getName()); } - vh.time.setText(StringUtils.friendly_time(item.getCreateTime())); + vh.time.setText(StringUtils.formatSomeAgo(item.getCreateTime())); vh.comment.setText(item.getReplyCount() + ""); if (item.getProject() != null && item.getProject().getGit() != null) { @@ -163,37 +162,37 @@ public class TeamIssueAdapter extends ListBaseAdapter { } static class ViewHolder { - @InjectView(R.id.iv_issue_state) + @Bind(R.id.iv_issue_state) TextView state; - @InjectView(R.id.tv_title) + @Bind(R.id.tv_title) TextView title; - @InjectView(R.id.iv_issue_source) + @Bind(R.id.iv_issue_source) TextView issueSource; - @InjectView(R.id.tv_project) + @Bind(R.id.tv_project) TextView project; - @InjectView(R.id.tv_attachments) + @Bind(R.id.tv_attachments) TextView attachments;// 附件 - @InjectView(R.id.tv_childissues) + @Bind(R.id.tv_childissues) TextView childissues;// 子任务 - @InjectView(R.id.tv_relations) + @Bind(R.id.tv_relations) TextView relations;// 关联任务 - @InjectView(R.id.tv_accept_time) + @Bind(R.id.tv_accept_time) TextView accept_time; - @InjectView(R.id.tv_author) + @Bind(R.id.tv_author) TextView author; - @InjectView(R.id.tv_to) + @Bind(R.id.tv_to) TextView to; - @InjectView(R.id.tv_touser) + @Bind(R.id.tv_touser) TextView touser; - @InjectView(R.id.tv_time) + @Bind(R.id.tv_time) TextView time; - @InjectView(R.id.tv_comment_count) + @Bind(R.id.tv_comment_count) TextView comment; - @InjectView(R.id.title) + @Bind(R.id.title) TextView title_line; public ViewHolder(View view) { - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); } } } diff --git a/app/src/main/java/net/oschina/app/team/adapter/TeamIssueCatalogAdapter.java b/app/src/main/java/net/oschina/app/team/adapter/TeamIssueCatalogAdapter.java index d54eef8781ccb2d15b1ec97938f819b5dc970678..3c1eca8a2571eedb7070c2b4081d9fb38deab4a8 100644 --- a/app/src/main/java/net/oschina/app/team/adapter/TeamIssueCatalogAdapter.java +++ b/app/src/main/java/net/oschina/app/team/adapter/TeamIssueCatalogAdapter.java @@ -1,63 +1,64 @@ package net.oschina.app.team.adapter; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + import net.oschina.app.R; import net.oschina.app.base.ListBaseAdapter; import net.oschina.app.team.bean.TeamIssueCatalog; import net.oschina.app.util.StringUtils; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; + +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; /** * TeamIssueCatalogAdapter.java - * + * * @author 火蚁(http://my.oschina.net/u/253900) - * * @data 2015-3-1 下午3:37:03 */ public class TeamIssueCatalogAdapter extends ListBaseAdapter { @Override protected View getRealView(int position, View convertView, ViewGroup parent) { - ViewHolder vh = null; - if (convertView == null || convertView.getTag() == null) { - convertView = getLayoutInflater(parent.getContext()).inflate( - R.layout.list_cell_team_issue_catalog, null); - vh = new ViewHolder(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } - - TeamIssueCatalog item = mDatas.get(position); - - vh.title.setText(item.getTitle()); - vh.state.setText(item.getOpenedIssueCount() + "/" - + item.getAllIssueCount()); - - String description = item.getDescription(); - if (description != null && !StringUtils.isEmpty(description)) { - vh.description.setText(description); - } else { - vh.description.setText("暂无描述"); - } - - return convertView; + ViewHolder vh = null; + if (convertView == null || convertView.getTag() == null) { + convertView = getLayoutInflater(parent.getContext()).inflate( + R.layout.list_cell_team_issue_catalog, null); + vh = new ViewHolder(convertView); + convertView.setTag(vh); + } else { + vh = (ViewHolder) convertView.getTag(); + } + + TeamIssueCatalog item = mDatas.get(position); + + vh.title.setText(item.getTitle()); + vh.state.setText(item.getOpenedIssueCount() + "/" + + item.getAllIssueCount()); + + String description = item.getDescription(); + if (description != null && !StringUtils.isEmpty(description)) { + vh.description.setText(description); + } else { + vh.description.setText("暂无描述"); + } + + return convertView; } static class ViewHolder { - @InjectView(R.id.tv_team_issue_catalog_title) - TextView title; - @InjectView(R.id.tv_team_issue_catalog_desc) - TextView description; - @InjectView(R.id.tv_team_issue_catalog_state) - TextView state; + @Bind(R.id.tv_team_issue_catalog_title) + TextView title; + @Bind(R.id.tv_team_issue_catalog_desc) + TextView description; + @Bind(R.id.tv_team_issue_catalog_state) + TextView state; - public ViewHolder(View view) { - ButterKnife.inject(this, view); - } + public ViewHolder(View view) { + ButterKnife.bind(this, view); + } } } diff --git a/app/src/main/java/net/oschina/app/team/adapter/TeamMemberAdapter.java b/app/src/main/java/net/oschina/app/team/adapter/TeamMemberAdapter.java index c729341782afcdbe7a2e4f20189d022956590782..f29a5f92f3a95a882909799a703e6ed62d34875c 100644 --- a/app/src/main/java/net/oschina/app/team/adapter/TeamMemberAdapter.java +++ b/app/src/main/java/net/oschina/app/team/adapter/TeamMemberAdapter.java @@ -1,13 +1,5 @@ package net.oschina.app.team.adapter; -import java.util.ArrayList; -import java.util.List; - -import net.oschina.app.R; -import net.oschina.app.team.bean.Team; -import net.oschina.app.team.bean.TeamMember; -import net.oschina.app.util.UIHelper; -import net.oschina.app.widget.AvatarView; import android.content.Context; import android.graphics.drawable.ColorDrawable; import android.view.View; @@ -17,11 +9,19 @@ import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; +import net.oschina.app.R; +import net.oschina.app.team.bean.Team; +import net.oschina.app.team.bean.TeamMember; +import net.oschina.app.util.UIHelper; +import net.oschina.app.widget.AvatarView; + +import java.util.ArrayList; +import java.util.List; + /** * 团队成员GridView适配器 - * + * * @author kymjs (kymjs123@gmail.com) - * */ public class TeamMemberAdapter extends BaseAdapter { private final Context cxt; diff --git a/app/src/main/java/net/oschina/app/team/adapter/TeamProjectListAdapter.java b/app/src/main/java/net/oschina/app/team/adapter/TeamProjectListAdapter.java index 843a260854400fa0db52bd88f37ae7c6daf15940..9f88a0f13c55673d6cda73fa613b28df97d72e4b 100644 --- a/app/src/main/java/net/oschina/app/team/adapter/TeamProjectListAdapter.java +++ b/app/src/main/java/net/oschina/app/team/adapter/TeamProjectListAdapter.java @@ -13,15 +13,14 @@ import net.oschina.app.util.TypefaceUtils; import java.util.ArrayList; import java.util.List; +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; /** * 团队项目适配器 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @version 创建时间:2015年1月19日 下午6:00:33 - * */ public class TeamProjectListAdapter extends BaseAdapter { @@ -88,13 +87,13 @@ public class TeamProjectListAdapter extends BaseAdapter { } public static class ViewHolder { - @InjectView(R.id.iv_source) + @Bind(R.id.iv_source) TextView source; - @InjectView(R.id.tv_project_name) + @Bind(R.id.tv_project_name) TextView name; public ViewHolder(View view) { - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); } } diff --git a/app/src/main/java/net/oschina/app/team/adapter/TeamProjectListAdapterNew.java b/app/src/main/java/net/oschina/app/team/adapter/TeamProjectListAdapterNew.java index 2654a37f66ac823ede90e07b311ecc0588f103dc..a7b4c5d6d60b81c5ee999f0cd2a5f923e69847f2 100644 --- a/app/src/main/java/net/oschina/app/team/adapter/TeamProjectListAdapterNew.java +++ b/app/src/main/java/net/oschina/app/team/adapter/TeamProjectListAdapterNew.java @@ -10,8 +10,8 @@ import net.oschina.app.base.ListBaseAdapter; import net.oschina.app.team.bean.TeamProject; import net.oschina.app.util.TypefaceUtils; +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; /** * 团队项目适配器 @@ -53,22 +53,24 @@ public class TeamProjectListAdapterNew extends ListBaseAdapter { TypefaceUtils.setTypeface(tvSource, R.string.fa_list_alt); } - vh.name.setText(item.getGit().getOwnerName() + " / " + item.getGit().getName()); - vh.issue.setText(item.getIssue().getOpened() + "/" + item.getIssue().getAll()); + vh.name.setText(String.format("%s / %s", item.getGit().getOwnerName(), item.getGit() + .getName())); + vh.issue.setText(String.format("%d/%d", item.getIssue().getOpened(), item.getIssue() + .getAll())); return convertView; } public static class ViewHolder { - @InjectView(R.id.iv_source) + @Bind(R.id.iv_source) TextView source; - @InjectView(R.id.tv_project_name) + @Bind(R.id.tv_project_name) TextView name; - @InjectView(R.id.tv_project_issue) + @Bind(R.id.tv_project_issue) TextView issue; public ViewHolder(View view) { - ButterKnife.inject(this, view); + ButterKnife.bind(this, view); } } diff --git a/app/src/main/java/net/oschina/app/team/adapter/TeamProjectMemberAdapter.java b/app/src/main/java/net/oschina/app/team/adapter/TeamProjectMemberAdapter.java index 2334c5908dba95f909883c810fdd21e9c870469f..e29bd5a54323dde2294b511a80c1ff42ee811b3c 100644 --- a/app/src/main/java/net/oschina/app/team/adapter/TeamProjectMemberAdapter.java +++ b/app/src/main/java/net/oschina/app/team/adapter/TeamProjectMemberAdapter.java @@ -1,55 +1,56 @@ package net.oschina.app.team.adapter; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + import net.oschina.app.R; import net.oschina.app.base.ListBaseAdapter; import net.oschina.app.team.bean.TeamMember; import net.oschina.app.widget.AvatarView; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; + +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; /** * 团队项目适配器 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @version 创建时间:2015年1月19日 下午6:00:33 - * */ public class TeamProjectMemberAdapter extends ListBaseAdapter { - + @Override protected View getRealView(int position, View convertView, ViewGroup parent) { - // TODO Auto-generated method stub - ViewHolder vh; - if (convertView == null || convertView.getTag() == null) { - convertView = View.inflate(parent.getContext(), - R.layout.list_cell_team_project_member, null); - vh = new ViewHolder(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } - - TeamMember item = mDatas.get(position); - - vh.avatar.setAvatarUrl(item.getPortrait()); - vh.name.setText(item.getName()); - - return convertView; + // TODO Auto-generated method stub + ViewHolder vh; + if (convertView == null || convertView.getTag() == null) { + convertView = View.inflate(parent.getContext(), + R.layout.list_cell_team_project_member, null); + vh = new ViewHolder(convertView); + convertView.setTag(vh); + } else { + vh = (ViewHolder) convertView.getTag(); + } + + TeamMember item = mDatas.get(position); + + vh.avatar.setAvatarUrl(item.getPortrait()); + vh.name.setText(item.getName()); + + return convertView; } public static class ViewHolder { - @InjectView(R.id.iv_avatar) - AvatarView avatar; - @InjectView(R.id.tv_name) - TextView name; - - public ViewHolder(View view) { - ButterKnife.inject(this, view); - } + @Bind(R.id.iv_avatar) + AvatarView avatar; + @Bind(R.id.tv_name) + TextView name; + + public ViewHolder(View view) { + ButterKnife.bind(this, view); + } } } diff --git a/app/src/main/java/net/oschina/app/team/adapter/TeamReplyAdapter.java b/app/src/main/java/net/oschina/app/team/adapter/TeamReplyAdapter.java index 93da945975880301ea0ef3381457f07b8669b38d..ae8d737fab29fdb2a9d2336d2d0f69f436b25c65 100644 --- a/app/src/main/java/net/oschina/app/team/adapter/TeamReplyAdapter.java +++ b/app/src/main/java/net/oschina/app/team/adapter/TeamReplyAdapter.java @@ -1,6 +1,10 @@ package net.oschina.app.team.adapter; -import java.util.List; +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; import net.oschina.app.R; import net.oschina.app.base.ListBaseAdapter; @@ -9,60 +13,57 @@ import net.oschina.app.util.HTMLUtil; import net.oschina.app.util.StringUtils; import net.oschina.app.widget.AvatarView; import net.oschina.app.widget.TweetTextView; -import android.content.Context; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; + +import java.util.List; + +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; /** * 评论适配器 TeamReply.java - * + * * @author 火蚁(http://my.oschina.net/u/253900) - * * @data 2015-1-30 下午4:05:00 */ public class TeamReplyAdapter extends ListBaseAdapter { @Override protected View getRealView(int position, View convertView, ViewGroup parent) { - ViewHolder vh = null; - if (convertView == null || convertView.getTag() == null) { - convertView = getLayoutInflater(parent.getContext()).inflate( - R.layout.list_cell_team_reply, null); - vh = new ViewHolder(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } + ViewHolder vh = null; + if (convertView == null || convertView.getTag() == null) { + convertView = getLayoutInflater(parent.getContext()).inflate( + R.layout.list_cell_team_reply, null); + vh = new ViewHolder(convertView); + convertView.setTag(vh); + } else { + vh = (ViewHolder) convertView.getTag(); + } - TeamReply item = mDatas.get(position); - vh.name.setText(item.getAuthor().getName()); - vh.avatar.setAvatarUrl(item.getAuthor().getPortrait()); - setContent(vh.content, HTMLUtil.delHTMLTag(item.getContent())); - vh.time.setText(StringUtils.friendly_time(item.getCreateTime())); + TeamReply item = mDatas.get(position); + vh.name.setText(item.getAuthor().getName()); + vh.avatar.setAvatarUrl(item.getAuthor().getPortrait()); + setContent(vh.content, HTMLUtil.delHTMLTag(item.getContent())); + vh.time.setText(StringUtils.formatSomeAgo(item.getCreateTime())); - if (StringUtils.isEmpty(item.getAppName())) { - vh.from.setVisibility(View.GONE); - } else { - vh.from.setVisibility(View.VISIBLE); - vh.from.setText(item.getAppName()); - } - setReplies(parent.getContext(), item, vh); - return convertView; + if (StringUtils.isEmpty(item.getAppName())) { + vh.from.setVisibility(View.GONE); + } else { + vh.from.setVisibility(View.VISIBLE); + vh.from.setText(item.getAppName()); + } + setReplies(parent.getContext(), item, vh); + return convertView; } private void setReplies(Context context, TeamReply item, ViewHolder vh) { - List replies = item.getReplies(); - vh.relies.removeAllViews(); - if (replies == null || replies.size() <= 0) { - vh.relies.setVisibility(View.GONE); - } else { - vh.relies.setVisibility(View.VISIBLE); - - // add count layout + List replies = item.getReplies(); + vh.relies.removeAllViews(); + if (replies == null || replies.size() <= 0) { + vh.relies.setVisibility(View.GONE); + } else { + vh.relies.setVisibility(View.VISIBLE); + + // add count layout View countView = getLayoutInflater(context).inflate( R.layout.list_cell_reply_count, null, false); TextView count = (TextView) countView @@ -70,50 +71,50 @@ public class TeamReplyAdapter extends ListBaseAdapter { count.setText(context.getResources().getString( R.string.comment_reply_count, replies.size())); vh.relies.addView(countView); - - for (TeamReply teamReply : replies) { - View replyItemView = getLayoutInflater( + + for (TeamReply teamReply : replies) { + View replyItemView = getLayoutInflater( context).inflate(R.layout.list_cell_team_reply_refers, null, false); - replyItemView.setBackgroundResource(R.drawable.comment_background); - - AvatarView avatarView = (AvatarView) replyItemView.findViewById(R.id.iv_avatar); - avatarView.setAvatarUrl(teamReply.getAuthor().getPortrait()); - TextView name = (TextView) replyItemView.findViewById(R.id.tv_name); - name.setText(teamReply.getAuthor().getName()); - TweetTextView content = (TweetTextView) replyItemView.findViewById(R.id.tv_content); - setContent(content, HTMLUtil.delHTMLTag(teamReply.getContent())); - TextView time = (TextView) replyItemView.findViewById(R.id.tv_time); - time.setText(StringUtils.friendly_time(teamReply.getCreateTime())); - TextView from = (TextView) replyItemView.findViewById(R.id.tv_from); - if (StringUtils.isEmpty(teamReply.getAppName())) { - from.setVisibility(View.GONE); - } else { - from.setVisibility(View.VISIBLE); - from.setText(teamReply.getAppName()); - } - vh.relies.addView(replyItemView); - } - } + replyItemView.setBackgroundResource(R.drawable.comment_background); + + AvatarView avatarView = (AvatarView) replyItemView.findViewById(R.id.iv_avatar); + avatarView.setAvatarUrl(teamReply.getAuthor().getPortrait()); + TextView name = (TextView) replyItemView.findViewById(R.id.tv_name); + name.setText(teamReply.getAuthor().getName()); + TweetTextView content = (TweetTextView) replyItemView.findViewById(R.id.tv_content); + setContent(content, HTMLUtil.delHTMLTag(teamReply.getContent())); + TextView time = (TextView) replyItemView.findViewById(R.id.tv_time); + time.setText(StringUtils.formatSomeAgo(teamReply.getCreateTime())); + TextView from = (TextView) replyItemView.findViewById(R.id.tv_from); + if (StringUtils.isEmpty(teamReply.getAppName())) { + from.setVisibility(View.GONE); + } else { + from.setVisibility(View.VISIBLE); + from.setText(teamReply.getAppName()); + } + vh.relies.addView(replyItemView); + } + } } static class ViewHolder { - @InjectView(R.id.iv_avatar) - AvatarView avatar; - @InjectView(R.id.tv_name) - TextView name; - @InjectView(R.id.tv_time) - TextView time; - @InjectView(R.id.tv_from) - TextView from; - @InjectView(R.id.tv_content) - TweetTextView content; - @InjectView(R.id.ly_relies) - LinearLayout relies; + @Bind(R.id.iv_avatar) + AvatarView avatar; + @Bind(R.id.tv_name) + TextView name; + @Bind(R.id.tv_time) + TextView time; + @Bind(R.id.tv_from) + TextView from; + @Bind(R.id.tv_content) + TweetTextView content; + @Bind(R.id.ly_relies) + LinearLayout relies; - public ViewHolder(View view) { - ButterKnife.inject(this, view); - } + public ViewHolder(View view) { + ButterKnife.bind(this, view); + } } } diff --git a/app/src/main/java/net/oschina/app/team/adapter/TeamSelectMemberAdapter.java b/app/src/main/java/net/oschina/app/team/adapter/TeamSelectMemberAdapter.java index 8b330aa3ea62f2c451b3800c9264bc19a2293fda..1bf180ccd6f1e6235075396b87616076f10e2842 100644 --- a/app/src/main/java/net/oschina/app/team/adapter/TeamSelectMemberAdapter.java +++ b/app/src/main/java/net/oschina/app/team/adapter/TeamSelectMemberAdapter.java @@ -1,72 +1,74 @@ package net.oschina.app.team.adapter; -import java.util.List; - -import net.oschina.app.R; -import net.oschina.app.team.bean.TeamMember; -import net.oschina.app.widget.AvatarView; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; + +import net.oschina.app.R; +import net.oschina.app.team.bean.TeamMember; +import net.oschina.app.widget.AvatarView; + +import java.util.List; + +import butterknife.Bind; import butterknife.ButterKnife; -import butterknife.InjectView; public class TeamSelectMemberAdapter extends BaseAdapter { static class ViewHolder { - @InjectView(R.id.iv_avatar) - AvatarView aView; - @InjectView(R.id.tv_name) - TextView name; + @Bind(R.id.iv_avatar) + AvatarView aView; + @Bind(R.id.tv_name) + TextView name; - ViewHolder(View view) { - ButterKnife.inject(this, view); - } + ViewHolder(View view) { + ButterKnife.bind(this, view); + } } private List members; public TeamSelectMemberAdapter(List members) { - this.members = members; + this.members = members; } @Override public int getCount() { - // TODO Auto-generated method stub - return members.size(); + // TODO Auto-generated method stub + return members.size(); } @Override public Object getItem(int position) { - // TODO Auto-generated method stub - return members.get(position); + // TODO Auto-generated method stub + return members.get(position); } @Override public long getItemId(int position) { - // TODO Auto-generated method stub - return position; + // TODO Auto-generated method stub + return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { - // TODO Auto-generated method stub - ViewHolder vh = null; - if (convertView == null || convertView.getTag() == null) { - LayoutInflater inflater = (LayoutInflater) parent.getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(R.layout.list_cell_team_project_member, null); - vh = new ViewHolder(convertView); - convertView.setTag(vh); - } else { - vh = (ViewHolder) convertView.getTag(); - } - TeamMember item = members.get(position); - vh.aView.setAvatarUrl(item.getPortrait()); - vh.name.setText(item.getName()); - return convertView; + // TODO Auto-generated method stub + ViewHolder vh = null; + if (convertView == null || convertView.getTag() == null) { + LayoutInflater inflater = (LayoutInflater) parent.getContext() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + convertView = inflater.inflate(R.layout.list_cell_team_project_member, null); + vh = new ViewHolder(convertView); + convertView.setTag(vh); + } else { + vh = (ViewHolder) convertView.getTag(); + } + TeamMember item = members.get(position); + vh.aView.setAvatarUrl(item.getPortrait()); + vh.name.setText(item.getName()); + return convertView; } } diff --git a/app/src/main/java/net/oschina/app/team/bean/MyIssueState.java b/app/src/main/java/net/oschina/app/team/bean/MyIssueState.java index ae52bf8f9ed22298cb402df94ee4f3733e04f27a..b12b5c40b926e4557985fccd10c758246462426a 100644 --- a/app/src/main/java/net/oschina/app/team/bean/MyIssueState.java +++ b/app/src/main/java/net/oschina/app/team/bean/MyIssueState.java @@ -1,14 +1,13 @@ package net.oschina.app.team.bean; -import net.oschina.app.bean.Entity; - import com.thoughtworks.xstream.annotations.XStreamAlias; +import net.oschina.app.bean.Entity; + /** * 我的任务中要显示的状态 - * + * * @author kymjs (https://github.com/kymjs) - * */ @SuppressWarnings("serial") @XStreamAlias("oschina") @@ -27,7 +26,7 @@ public class MyIssueState extends Entity { private String accepted; // 已验收 @XStreamAlias("all") private String all; - + @XStreamAlias("member") private TeamMember user; diff --git a/app/src/main/java/net/oschina/app/team/bean/Team.java b/app/src/main/java/net/oschina/app/team/bean/Team.java index ec6ae221e916749452db780937d62de6009e7a4f..9dbfa66d93f424d3fde312913c5d09473aa674d5 100644 --- a/app/src/main/java/net/oschina/app/team/bean/Team.java +++ b/app/src/main/java/net/oschina/app/team/bean/Team.java @@ -1,14 +1,13 @@ package net.oschina.app.team.bean; -import net.oschina.app.bean.Entity; - import com.thoughtworks.xstream.annotations.XStreamAlias; +import net.oschina.app.bean.Entity; + /** * 团队属性 - * + * * @author kymjs - * */ @SuppressWarnings("serial") @XStreamAlias("team") diff --git a/app/src/main/java/net/oschina/app/team/bean/TeamActive.java b/app/src/main/java/net/oschina/app/team/bean/TeamActive.java index e1786d84c527838128364af84553c14c54209289..b027235d07210af32f760a2c9bdd5db140571dcd 100644 --- a/app/src/main/java/net/oschina/app/team/bean/TeamActive.java +++ b/app/src/main/java/net/oschina/app/team/bean/TeamActive.java @@ -1,14 +1,13 @@ package net.oschina.app.team.bean; -import net.oschina.app.bean.Entity; - import com.thoughtworks.xstream.annotations.XStreamAlias; +import net.oschina.app.bean.Entity; + /** * Team模块的动态JavaBean - * + * * @author kymjs - * */ @SuppressWarnings("serial") @XStreamAlias("active") diff --git a/app/src/main/java/net/oschina/app/team/bean/TeamActiveDetail.java b/app/src/main/java/net/oschina/app/team/bean/TeamActiveDetail.java index 25ce3afe681ad45e63c007a07ef311d7117f9f07..277e78a5fa33d05f8043626a259ce5af3f3c6ebb 100644 --- a/app/src/main/java/net/oschina/app/team/bean/TeamActiveDetail.java +++ b/app/src/main/java/net/oschina/app/team/bean/TeamActiveDetail.java @@ -1,14 +1,13 @@ package net.oschina.app.team.bean; -import net.oschina.app.bean.Entity; - import com.thoughtworks.xstream.annotations.XStreamAlias; +import net.oschina.app.bean.Entity; + /** * Team模块的动态JavaBean - * + * * @author kymjs - * */ @SuppressWarnings("serial") @XStreamAlias("oschina") diff --git a/app/src/main/java/net/oschina/app/team/bean/TeamActives.java b/app/src/main/java/net/oschina/app/team/bean/TeamActives.java index facd05912c96269bf307d509cd56b4c42b7a7617..e1e05149906b119d655a7d1c5f4691e595626eff 100644 --- a/app/src/main/java/net/oschina/app/team/bean/TeamActives.java +++ b/app/src/main/java/net/oschina/app/team/bean/TeamActives.java @@ -1,18 +1,17 @@ package net.oschina.app.team.bean; -import java.util.ArrayList; -import java.util.List; +import com.thoughtworks.xstream.annotations.XStreamAlias; import net.oschina.app.bean.Entity; import net.oschina.app.bean.ListEntity; -import com.thoughtworks.xstream.annotations.XStreamAlias; +import java.util.ArrayList; +import java.util.List; /** * 动态列表 - * + * * @author kymjs - * */ @XStreamAlias("oschina") public class TeamActives extends Entity implements ListEntity { diff --git a/app/src/main/java/net/oschina/app/team/bean/TeamDiary.java b/app/src/main/java/net/oschina/app/team/bean/TeamDiary.java index ddcbe79fc154dbfb26b2bfb6f412fc216d703395..cf8e3eebea77cc3aa254767332aaa470f710a5fe 100644 --- a/app/src/main/java/net/oschina/app/team/bean/TeamDiary.java +++ b/app/src/main/java/net/oschina/app/team/bean/TeamDiary.java @@ -1,9 +1,9 @@ package net.oschina.app.team.bean; -import net.oschina.app.bean.Entity; - import com.thoughtworks.xstream.annotations.XStreamAlias; +import net.oschina.app.bean.Entity; + @SuppressWarnings("serial") @XStreamAlias("diary") public class TeamDiary extends Entity { diff --git a/app/src/main/java/net/oschina/app/team/bean/TeamDiaryDetailBean.java b/app/src/main/java/net/oschina/app/team/bean/TeamDiaryDetailBean.java index fb315309bbf459d8207e9147a0830fcac107fd42..76e6c8ab7870cf6c0851a7fff18035835f1fada0 100644 --- a/app/src/main/java/net/oschina/app/team/bean/TeamDiaryDetailBean.java +++ b/app/src/main/java/net/oschina/app/team/bean/TeamDiaryDetailBean.java @@ -1,9 +1,9 @@ package net.oschina.app.team.bean; -import net.oschina.app.bean.Entity; - import com.thoughtworks.xstream.annotations.XStreamAlias; +import net.oschina.app.bean.Entity; + @XStreamAlias("oschina") public class TeamDiaryDetailBean extends Entity { diff --git a/app/src/main/java/net/oschina/app/team/bean/TeamDiaryList.java b/app/src/main/java/net/oschina/app/team/bean/TeamDiaryList.java index ac64584becdfc5826f080bce39e5e6522f545b5f..7fdd48783383219657dd9594b0246e7e6564f0fd 100644 --- a/app/src/main/java/net/oschina/app/team/bean/TeamDiaryList.java +++ b/app/src/main/java/net/oschina/app/team/bean/TeamDiaryList.java @@ -1,48 +1,48 @@ package net.oschina.app.team.bean; -import java.util.ArrayList; -import java.util.List; - import com.thoughtworks.xstream.annotations.XStreamAlias; import net.oschina.app.bean.Entity; import net.oschina.app.bean.ListEntity; +import java.util.ArrayList; +import java.util.List; + @SuppressWarnings("serial") @XStreamAlias("oschina") public class TeamDiaryList extends Entity implements ListEntity { - - @XStreamAlias("pagesize") - private int pageSize; - - @XStreamAlias("totalCount") - private int totalCount; - - @XStreamAlias("diaries") - private List list = new ArrayList(); - - public int getPageSize() { - return pageSize; - } - - public void setPageSize(int pageSize) { - this.pageSize = pageSize; - } - - public int getTotalCount() { - return totalCount; - } - - public void setTotalCount(int totalCount) { - this.totalCount = totalCount; - } - - public List getList() { - return list; - } - - public void setList(List list) { - this.list = list; - } - + + @XStreamAlias("pagesize") + private int pageSize; + + @XStreamAlias("totalCount") + private int totalCount; + + @XStreamAlias("diaries") + private List list = new ArrayList(); + + public int getPageSize() { + return pageSize; + } + + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + } + + public int getTotalCount() { + return totalCount; + } + + public void setTotalCount(int totalCount) { + this.totalCount = totalCount; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + } diff --git a/app/src/main/java/net/oschina/app/team/bean/TeamDiscuss.java b/app/src/main/java/net/oschina/app/team/bean/TeamDiscuss.java index 05150baa58af7e9523699d1310e057e2432ec1b5..4118173b0d945dbc539fc47d87fd8bb78c4d2eb4 100644 --- a/app/src/main/java/net/oschina/app/team/bean/TeamDiscuss.java +++ b/app/src/main/java/net/oschina/app/team/bean/TeamDiscuss.java @@ -30,58 +30,58 @@ public class TeamDiscuss extends Entity { private Author author; public String getType() { - return type; + return type; } public void setType(String type) { - this.type = type; + this.type = type; } public String getTitle() { - return title; + return title; } public void setTitle(String title) { - this.title = title; + this.title = title; } public String getBody() { - return body; + return body; } public void setBody(String body) { - this.body = body; + this.body = body; } public String getCreateTime() { - return createTime; + return createTime; } public void setCreateTime(String createTime) { - this.createTime = createTime; + this.createTime = createTime; } public int getAnswerCount() { - return answerCount; + return answerCount; } public void setAnswerCount(int answerCount) { - this.answerCount = answerCount; + this.answerCount = answerCount; } public int getVoteUp() { - return voteUp; + return voteUp; } public void setVoteUp(int voteUp) { - this.voteUp = voteUp; + this.voteUp = voteUp; } public Author getAuthor() { - return author; + return author; } public void setAuthor(Author author) { - this.author = author; + this.author = author; } } diff --git a/app/src/main/java/net/oschina/app/team/bean/TeamDiscussDetail.java b/app/src/main/java/net/oschina/app/team/bean/TeamDiscussDetail.java index 6769b072542a085885ced4c3f6012ed3e4fb1d69..d583f25476e6265a827190ae05fdf2e7ca3ef028 100644 --- a/app/src/main/java/net/oschina/app/team/bean/TeamDiscussDetail.java +++ b/app/src/main/java/net/oschina/app/team/bean/TeamDiscussDetail.java @@ -6,15 +6,14 @@ import net.oschina.app.bean.Entity; /** * TeamDisscussDetail.java - * - * @author 火蚁(http://my.oschina.net/u/253900) * + * @author 火蚁(http://my.oschina.net/u/253900) * @data 2015-2-2 下午5:33:07 */ @SuppressWarnings("serial") @XStreamAlias("oschina") public class TeamDiscussDetail extends Entity { - + @XStreamAlias("discuss") private TeamDiscuss discuss; diff --git a/app/src/main/java/net/oschina/app/team/bean/TeamDiscussList.java b/app/src/main/java/net/oschina/app/team/bean/TeamDiscussList.java index 6af54ded0b53c4f31764de09a7548ce7dec683fa..aa4057b00388f356c4b90ee0b66100e55ca475cd 100644 --- a/app/src/main/java/net/oschina/app/team/bean/TeamDiscussList.java +++ b/app/src/main/java/net/oschina/app/team/bean/TeamDiscussList.java @@ -1,48 +1,48 @@ package net.oschina.app.team.bean; -import java.util.ArrayList; -import java.util.List; - import com.thoughtworks.xstream.annotations.XStreamAlias; import net.oschina.app.bean.Entity; import net.oschina.app.bean.ListEntity; +import java.util.ArrayList; +import java.util.List; + @SuppressWarnings("serial") @XStreamAlias("oschina") public class TeamDiscussList extends Entity implements ListEntity { - - @XStreamAlias("pagesize") - private int pageSize; - - @XStreamAlias("totalCount") - private int totalCount; - - @XStreamAlias("discusses") - private List list = new ArrayList(); - - public int getPageSize() { - return pageSize; - } - - public void setPageSize(int pageSize) { - this.pageSize = pageSize; - } - - public int getTotalCount() { - return totalCount; - } - - public void setTotalCount(int totalCount) { - this.totalCount = totalCount; - } - - public List getList() { - return list; - } - - public void setList(List list) { - this.list = list; - } - + + @XStreamAlias("pagesize") + private int pageSize; + + @XStreamAlias("totalCount") + private int totalCount; + + @XStreamAlias("discusses") + private List list = new ArrayList(); + + public int getPageSize() { + return pageSize; + } + + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + } + + public int getTotalCount() { + return totalCount; + } + + public void setTotalCount(int totalCount) { + this.totalCount = totalCount; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + } diff --git a/app/src/main/java/net/oschina/app/team/bean/TeamGit.java b/app/src/main/java/net/oschina/app/team/bean/TeamGit.java index 9a16ca469a603c7954b001caa1686d38a5ca0aa4..bc2a936444a43d91d1dcc9238bbff31c07a7b023 100644 --- a/app/src/main/java/net/oschina/app/team/bean/TeamGit.java +++ b/app/src/main/java/net/oschina/app/team/bean/TeamGit.java @@ -7,7 +7,6 @@ import net.oschina.app.bean.Entity; /** * @author FireAnt(http://my.oschina.net/LittleDY) * @version 创建时间:2015年1月20日 上午10:49:39 - * */ @SuppressWarnings("serial") @@ -27,42 +26,42 @@ public class TeamGit extends Entity { private String ownerUserName; public int getId() { - return id; + return id; } public void setId(int id) { - this.id = id; + this.id = id; } public String getName() { - return name; + return name; } public void setName(String name) { - this.name = name; + this.name = name; } public String getPath() { - return path; + return path; } public void setPath(String path) { - this.path = path; + this.path = path; } public String getOwnerName() { - return ownerName; + return ownerName; } public void setOwnerName(String ownerName) { - this.ownerName = ownerName; + this.ownerName = ownerName; } public String getOwnerUserName() { - return ownerUserName; + return ownerUserName; } public void setOwnerUserName(String ownerUserName) { - this.ownerUserName = ownerUserName; + this.ownerUserName = ownerUserName; } } diff --git a/app/src/main/java/net/oschina/app/team/bean/TeamIssue.java b/app/src/main/java/net/oschina/app/team/bean/TeamIssue.java index 6ac084bf431f44fcbf4c9937d9e5c89f07204d22..e40f563d5241fb97d0deb59020abe333b0d1d220 100644 --- a/app/src/main/java/net/oschina/app/team/bean/TeamIssue.java +++ b/app/src/main/java/net/oschina/app/team/bean/TeamIssue.java @@ -1,5 +1,12 @@ package net.oschina.app.team.bean; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamImplicit; + +import net.oschina.app.R; +import net.oschina.app.bean.Entity; +import net.oschina.app.util.StringUtils; + import java.io.Serializable; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -7,19 +14,11 @@ import java.util.Calendar; import java.util.Date; import java.util.List; -import net.oschina.app.R; -import net.oschina.app.bean.Entity; -import net.oschina.app.util.StringUtils; - -import com.thoughtworks.xstream.annotations.XStreamAlias; -import com.thoughtworks.xstream.annotations.XStreamImplicit; - /** * 任务实体类 - * + * * @author FireAnt(http://my.oschina.net/LittleDY) * @version 创建时间:2015年1月14日 下午4:26:28 - * */ @SuppressWarnings("serial") @@ -101,484 +100,484 @@ public class TeamIssue extends Entity { private Attachments attachments;// 附件数量 public String getState() { - return state; + return state; } public void setState(String state) { - this.state = state; + this.state = state; } public int getStateLevel() { - return stateLevel; + return stateLevel; } public void setStateLevel(int stateLevel) { - this.stateLevel = stateLevel; + this.stateLevel = stateLevel; } public String getPriority() { - return priority; + return priority; } public void setPriority(String priority) { - this.priority = priority; + this.priority = priority; } public int getGitpush() { - return gitpush; + return gitpush; } public void setGitpush(int gitpush) { - this.gitpush = gitpush; + this.gitpush = gitpush; } public TeamProject getProject() { - return project; + return project; } public void setProject(TeamProject project) { - this.project = project; + this.project = project; } public TeamIssueChild getChildIssues() { - return childIssues; + return childIssues; } public void setChildIssues(TeamIssueChild childIssues) { - this.childIssues = childIssues; + this.childIssues = childIssues; } public Relations getRelations() { - return relations; + return relations; } public void setRelations(Relations relations) { - this.relations = relations; + this.relations = relations; } public Attachments getAttachments() { - return attachments; + return attachments; } public void setAttachments(Attachments attachments) { - this.attachments = attachments; + this.attachments = attachments; } public String getSource() { - return source; + return source; } public void setSource(String source) { - this.source = source; + this.source = source; } public String getTitle() { - return title; + return title; } public void setTitle(String title) { - this.title = title; + this.title = title; } public String getDescription() { - return description; + return description; } public void setDescription(String description) { - this.description = description; + this.description = description; } public String getCreateTime() { - return createTime; + return createTime; } public void setCreateTime(String createTime) { - this.createTime = createTime; + this.createTime = createTime; } public String getUpdateTime() { - return updateTime; + return updateTime; } public void setUpdateTime(String updateTime) { - this.updateTime = updateTime; + this.updateTime = updateTime; } public String getAcceptTime() { - return acceptTime; + return acceptTime; } public void setAcceptTime(String acceptTime) { - this.acceptTime = acceptTime; + this.acceptTime = acceptTime; } public String getDeadlineTime() { - return deadlineTime; + return deadlineTime; } public void setDeadlineTime(String deadlineTime) { - this.deadlineTime = deadlineTime; + this.deadlineTime = deadlineTime; } public Author getAuthor() { - return author; + return author; } public void setAuthor(Author author) { - this.author = author; + this.author = author; } public ToUser getToUser() { - return toUser; + return toUser; } public void setToUser(ToUser toUser) { - this.toUser = toUser; + this.toUser = toUser; } public List