├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── scopes │ └── scope_settings.xml └── vcs.xml ├── NyaSama.iml ├── README.md ├── app ├── .gitignore ├── app.iml ├── assets │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── BGM.ogg │ ├── BIU.ogg │ ├── Back01.png │ ├── Back02.png │ ├── Bullet.png │ ├── Graze.ogg │ ├── Nyasama.png │ └── Player.png ├── build.gradle ├── libs │ ├── armeabi-v7a │ │ ├── libgdx-box2d.so │ │ └── libgdx.so │ ├── armeabi │ │ ├── libgdx-box2d.so │ │ └── libgdx.so │ └── x86 │ │ ├── libgdx-box2d.so │ │ └── libgdx.so ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── nyasama │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── nyasama │ │ ├── ThisApp.java │ │ ├── activity │ │ ├── AboutActivity.java │ │ ├── AttachmentViewer.java │ │ ├── BaseThemedActivity.java │ │ ├── FavListActivity.java │ │ ├── LoginActivity.java │ │ ├── MainActivity.java │ │ ├── MessagesActivity.java │ │ ├── NewPostActivity.java │ │ ├── NoticeActivity.java │ │ ├── PMListActivity.java │ │ ├── PostListActivity.java │ │ ├── SearchActivity.java │ │ ├── SettingActivity.java │ │ ├── SplashActivity.java │ │ ├── ThreadListActivity.java │ │ └── UserProfileActivity.java │ │ ├── fragment │ │ ├── CommonListFragment.java │ │ ├── DiscuzComicListFragment.java │ │ ├── DiscuzForumIndexFragment.java │ │ ├── DiscuzThreadListFragment.java │ │ ├── DiscuzTopListFragment.java │ │ ├── NavigationDrawerFragment.java │ │ └── SimpleLayoutFragment.java │ │ ├── libgdx │ │ └── AboutCore.java │ │ └── util │ │ ├── BitmapLruCache.java │ │ ├── CallbackMatcher.java │ │ ├── CommonListAdapter.java │ │ ├── Discuz.java │ │ ├── Helper.java │ │ ├── HtmlImageGetter.java │ │ ├── InMemoryCookieStore.java │ │ ├── MultipartRequest.java │ │ ├── PersistenceCookieStore.java │ │ ├── PhotoViewPager.java │ │ ├── SafeHtmlText.java │ │ └── SimpleIndicator.java │ └── res │ ├── drawable-hdpi │ ├── drawer_shadow.9.png │ ├── ic_action_new.png │ ├── ic_action_reply.png │ ├── ic_action_save.png │ ├── ic_action_search.png │ ├── ic_action_send_now.png │ ├── ic_drawer.png │ ├── ic_drawer_dark.png │ ├── ic_launcher.png │ └── ic_menu_emoticons.png │ ├── drawable-mdpi │ ├── drawer_shadow.9.png │ ├── ic_action_new.png │ ├── ic_action_reply.png │ ├── ic_action_save.png │ ├── ic_action_search.png │ ├── ic_action_send_now.png │ ├── ic_drawer.png │ ├── ic_drawer_dark.png │ ├── ic_launcher.png │ └── ic_menu_emoticons.png │ ├── drawable-xhdpi │ ├── drawer_shadow.9.png │ ├── ic_action_new.png │ ├── ic_action_reply.png │ ├── ic_action_save.png │ ├── ic_action_search.png │ ├── ic_action_send_now.png │ ├── ic_drawer.png │ ├── ic_drawer_dark.png │ ├── ic_launcher.png │ └── ic_menu_emoticons.png │ ├── drawable-xxhdpi │ ├── drawer_shadow.9.png │ ├── ic_action_new.png │ ├── ic_action_reply.png │ ├── ic_action_save.png │ ├── ic_action_search.png │ ├── ic_action_send_now.png │ ├── ic_drawer.png │ ├── ic_drawer_dark.png │ ├── ic_launcher.png │ └── ic_menu_emoticons.png │ ├── drawable │ ├── ab_icon.png │ ├── default_board_icon.jpg │ ├── dot_indicator.xml │ ├── joessr.png │ ├── splash_anim.xml │ ├── splash_anim1.png │ └── splash_anim2.png │ ├── layout │ ├── activity_attachment_viewer.xml │ ├── activity_login.xml │ ├── activity_main.xml │ ├── activity_new_post.xml │ ├── activity_simple_framelayout.xml │ ├── activity_splash_screen.xml │ ├── activity_thread_list.xml │ ├── activity_user_profile.xml │ ├── dialog_ban_post.xml │ ├── dialog_bump_thread.xml │ ├── dialog_delete_post.xml │ ├── dialog_delete_thread.xml │ ├── dialog_digest_thread.xml │ ├── dialog_highlight_thread.xml │ ├── dialog_move_thread.xml │ ├── dialog_open_thread.xml │ ├── dialog_stick_thread.xml │ ├── dialog_warn_post.xml │ ├── fragment_attachment_image.xml │ ├── fragment_attachment_indicator.xml │ ├── fragment_attachment_item.xml │ ├── fragment_blank.xml │ ├── fragment_fav_item.xml │ ├── fragment_forum_cat_item.xml │ ├── fragment_forum_item.xml │ ├── fragment_main_home.xml │ ├── fragment_navigation_drawer.xml │ ├── fragment_new_poll.xml │ ├── fragment_notice_item.xml │ ├── fragment_pm_from_me.xml │ ├── fragment_pm_from_others.xml │ ├── fragment_pmlist_item.xml │ ├── fragment_post_item.xml │ ├── fragment_select_attachment_item.xml │ ├── fragment_simple_grid.xml │ ├── fragment_simple_list.xml │ ├── fragment_simple_list_loading.xml │ ├── fragment_smiley_item.xml │ ├── fragment_spinner_item_2.xml │ ├── fragment_thread_grid.xml │ ├── fragment_thread_item.xml │ ├── fragment_upload_process.xml │ ├── include_dialog_moderate_expiration.xml │ ├── include_dialog_moderate_reason.xml │ ├── include_indicator_right.xml │ ├── include_sep_horizontal.xml │ └── include_sep_vertical.xml │ ├── menu │ ├── global.xml │ ├── main.xml │ ├── menu_fav_list.xml │ ├── menu_login.xml │ ├── menu_messages.xml │ ├── menu_new_post.xml │ ├── menu_notice.xml │ ├── menu_pm_list.xml │ ├── menu_post_item.xml │ ├── menu_post_list.xml │ ├── menu_search.xml │ ├── menu_setting.xml │ ├── menu_thread_list.xml │ └── menu_user_profile.xml │ ├── values-en │ └── strings.xml │ ├── values-ja │ └── strings.xml │ ├── values-w820dp │ └── dimens.xml │ ├── values │ ├── dimens.xml │ ├── login_question.xml │ ├── preference_values.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── preference.xml │ └── searchable.xml ├── build.gradle ├── extra └── app_icon.xcf ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | .log 8 | /release -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | NyaSama -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NyaSama.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NyaSama 2 | ======= 3 | 4 | 这里是 [喵玉殿](http://bbs.nyasama.com/) Android 客户端的咪 5 | 6 | ## Features 7 | 与已经停止更新的 Discuz 客户端 掌上论坛 不同, 8 | 这个客户端基于原生界面重新实现了论坛客户端的基本功能。 9 | 结合后端修改过的 [DiscuzMobile](https://github.com/NSDN/DiscuzMobile) 插件, 10 | 提供的拓展功能还包括 11 | 12 | * 帖子点评,投票,编辑,搜索等 13 | * 用户提醒和消息通知 14 | * 发送表情,自定板块图标,浏览图片附件的大图模式 15 | * 论坛签到等插件的支持 16 | * 帖子的删除,移动,置顶等(管理员) 17 | 18 | 19 | ## Contributing 20 | 请阅读 wiki 上的 [路线图](https://github.com/NSDN/NyaSama/wiki/Plan) 和 [待办事项](https://github.com/NSDN/NyaSama/wiki/TODOs) 21 | 22 | ## Credits 23 | 这个项目使用了以下开源项目提供的功能 24 | 25 | * [PhotoView](https://github.com/chrisbanes/PhotoView) 26 | * [libGDX](https://github.com/libGDX/libGDX) 27 | * [AndroidStaggeredGrid](https://github.com/etsy/AndroidStaggeredGrid) 28 | * [holoaccent](https://github.com/negusoft/holoaccent) 29 | * [DiskLruCache](https://github.com/JakeWharton/DiskLruCache) 30 | * [ACRA](https://github.com/ACRA/acra) 31 | 32 | 这个项目使用了 [OpenJDK](http://openjdk.java.net/) 中的源代码 33 | 34 | ## License 35 | GPL v2 36 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /app/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/assets/1.png -------------------------------------------------------------------------------- /app/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/assets/2.png -------------------------------------------------------------------------------- /app/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/assets/3.png -------------------------------------------------------------------------------- /app/assets/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/assets/4.png -------------------------------------------------------------------------------- /app/assets/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/assets/5.png -------------------------------------------------------------------------------- /app/assets/BGM.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/assets/BGM.ogg -------------------------------------------------------------------------------- /app/assets/BIU.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/assets/BIU.ogg -------------------------------------------------------------------------------- /app/assets/Back01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/assets/Back01.png -------------------------------------------------------------------------------- /app/assets/Back02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/assets/Back02.png -------------------------------------------------------------------------------- /app/assets/Bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/assets/Bullet.png -------------------------------------------------------------------------------- /app/assets/Graze.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/assets/Graze.ogg -------------------------------------------------------------------------------- /app/assets/Nyasama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/assets/Nyasama.png -------------------------------------------------------------------------------- /app/assets/Player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/assets/Player.png -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 20 5 | buildToolsVersion '20.0.0' 6 | defaultConfig { 7 | applicationId 'com.nyasama' 8 | minSdkVersion 15 9 | targetSdkVersion 20 10 | versionCode 1 11 | versionName 'nightly-debug-no-update' 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | packagingOptions { 20 | exclude 'META-INF/DEPENDENCIES' 21 | exclude 'META-INF/NOTICE' 22 | exclude 'META-INF/LICENSE' 23 | exclude 'META-INF/LICENSE.txt' 24 | exclude 'META-INF/NOTICE.txt' 25 | } 26 | productFlavors { 27 | } 28 | sourceSets { 29 | main { 30 | assets.srcDirs = ['assets'] 31 | } 32 | } 33 | } 34 | 35 | task copyAndroidNatives() { 36 | file("libs/armeabi/").mkdirs(); 37 | file("libs/armeabi-v7a/").mkdirs(); 38 | file("libs/x86/").mkdirs(); 39 | 40 | configurations.natives.files.each { jar -> 41 | def outputDir = null 42 | if(jar.name.endsWith("natives-armeabi-v7a.jar")) outputDir = file("libs/armeabi-v7a") 43 | if(jar.name.endsWith("natives-armeabi.jar")) outputDir = file("libs/armeabi") 44 | if(jar.name.endsWith("natives-x86.jar")) outputDir = file("libs/x86") 45 | if(outputDir != null) { 46 | copy { 47 | from zipTree(jar) 48 | into outputDir 49 | include "*.so" 50 | } 51 | } 52 | } 53 | } 54 | 55 | tasks.withType(com.android.build.gradle.tasks.PackageApplication) { pkgTask -> 56 | pkgTask.jniFolders = new HashSet() 57 | pkgTask.jniFolders.add(new File(projectDir, 'libs')) 58 | } 59 | 60 | dependencies { 61 | compile fileTree(include: ['*.jar'], dir: 'libs') 62 | compile 'com.android.support:support-v4:21.0.2' 63 | compile 'com.mcxiaoke.volley:library-aar:1.0.0' 64 | compile 'com.github.chrisbanes.photoview:library:1.2.3' 65 | // adding this dependency causes gradle problem 66 | // REF: http://stackoverflow.com/questions/20673888/duplicate-files-copied-android-studio-0-4-0 67 | compile 'org.apache.httpcomponents:httpmime:4.2.2' 68 | compile 'com.badlogicgames.gdx:gdx:1.5.0' 69 | compile "com.badlogicgames.gdx:gdx-backend-android:1.5.0" 70 | compile 'com.etsy.android.grid:library:1.0.5' 71 | compile 'com.negusoft.holoaccent:library:1.1' 72 | compile 'com.jakewharton:disklrucache:2.0.2' 73 | compile 'ch.acra:acra:4.6.2' 74 | } 75 | -------------------------------------------------------------------------------- /app/libs/armeabi-v7a/libgdx-box2d.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/libs/armeabi-v7a/libgdx-box2d.so -------------------------------------------------------------------------------- /app/libs/armeabi-v7a/libgdx.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/libs/armeabi-v7a/libgdx.so -------------------------------------------------------------------------------- /app/libs/armeabi/libgdx-box2d.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/libs/armeabi/libgdx-box2d.so -------------------------------------------------------------------------------- /app/libs/armeabi/libgdx.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/libs/armeabi/libgdx.so -------------------------------------------------------------------------------- /app/libs/x86/libgdx-box2d.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/libs/x86/libgdx-box2d.so -------------------------------------------------------------------------------- /app/libs/x86/libgdx.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/libs/x86/libgdx.so -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Programs\adk\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/nyasama/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.nyasama; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 17 | 18 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 55 | 56 | 59 | 60 | 63 | 64 | 65 | 130 | 131 | 134 | 135 | 138 | 139 | 142 | 143 | 146 | 147 | 150 | 151 | 154 | 155 | 158 | 159 | 163 | 164 | 168 | 169 | 170 | 171 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/ThisApp.java: -------------------------------------------------------------------------------- 1 | package com.nyasama; 2 | 3 | import android.app.AlarmManager; 4 | import android.app.Application; 5 | import android.app.PendingIntent; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.SharedPreferences; 9 | import android.content.pm.PackageManager; 10 | import android.content.res.Configuration; 11 | import android.preference.PreferenceManager; 12 | import android.webkit.WebView; 13 | 14 | import com.android.volley.Cache; 15 | import com.android.volley.Network; 16 | import com.android.volley.RequestQueue; 17 | import com.android.volley.toolbox.BasicNetwork; 18 | import com.android.volley.toolbox.DiskBasedCache; 19 | import com.android.volley.toolbox.HurlStack; 20 | import com.android.volley.toolbox.ImageLoader; 21 | import com.jakewharton.disklrucache.DiskLruCache; 22 | import com.nyasama.activity.SplashActivity; 23 | import com.nyasama.util.BitmapLruCache; 24 | import com.nyasama.util.Helper; 25 | import com.nyasama.util.PersistenceCookieStore; 26 | 27 | import org.acra.ACRA; 28 | import org.acra.ReportingInteractionMode; 29 | import org.acra.annotation.ReportsCrashes; 30 | import org.acra.sender.HttpSender; 31 | 32 | import java.io.File; 33 | import java.io.IOException; 34 | import java.net.CookieHandler; 35 | import java.net.CookieManager; 36 | import java.net.CookiePolicy; 37 | import java.net.HttpURLConnection; 38 | import java.net.URL; 39 | import java.util.Locale; 40 | 41 | /** 42 | * Created by oxyflour on 2014/11/15. 43 | * 44 | * application 类里表示了本程序需要的最基本的设定和工具 45 | * 46 | * Volley 是 google 开发的工具包,它提供了更加方便的从 URL 载入图片的方法 47 | * Volley 的函数文档: 48 | * http://afzaln.com/volley/ 49 | * 50 | * Android developer 文档: 51 | * http://developer.android.com/training/volley/index.html 52 | * 53 | * 不过这里的调用方法和 Android developer 文档中的方法不大一样,直接参考 javadoc 比较好 54 | * 55 | * Volley 类: 56 | * requestQueue:是一个 HTML 请求队列,调用 start() 后,就会不断地把队列中的请求发到对应的 URL 去以获得数据 57 | * imageLoader: 后台线程,用来下载 URL 中的图片并装载到组件上(调用见 MainActivity 中的 Avatar) 58 | * volleyCache: 缓存 HTML response 的储存器,imageLoader 另有别的储存器 59 | * 60 | * webView 类: 61 | * webView: 网页浏览器,用于运行javascript代码,用例见util.Discuz 62 | * cookieStore: 存储 cookie,目前用处不明,用例见util.Discuz 63 | * 64 | */ 65 | @ReportsCrashes( 66 | formUri = "https://nsdn.cloudant.com/acra-nyasama/_design/acra-storage/_update/report", 67 | reportType = HttpSender.Type.JSON, 68 | httpMethod = HttpSender.Method.PUT, 69 | formUriBasicAuthLogin="diatimakedlyredgaingires", 70 | formUriBasicAuthPassword="GHxhyFvXLUoHAqMVkgCLSboe", 71 | mode = ReportingInteractionMode.TOAST, 72 | resToastText = R.string.acra_reporting_error) 73 | 74 | public class ThisApp extends Application { 75 | public static SharedPreferences preferences; 76 | public static Context context; 77 | public static Cache volleyCache; 78 | public static RequestQueue requestQueue; 79 | public static ImageLoader imageLoader; 80 | public static PersistenceCookieStore cookieStore; 81 | public static WebView webView; 82 | public static DiskLruCache fileDiskCache; 83 | 84 | // 下面的两个函数从sharedpreference 中读取语言设置 85 | private static Locale getLocale(String language) { 86 | String[] values = context.getResources().getStringArray(R.array.language_preference); 87 | Locale[] locales = { 88 | Locale.getDefault(), 89 | Locale.SIMPLIFIED_CHINESE, 90 | Locale.ENGLISH 91 | }; 92 | for (int i = 0; i < locales.length && i < values.length; i ++) 93 | if (values[i].equals(language)) return locales[i]; 94 | return Locale.getDefault(); 95 | } 96 | 97 | // REF: http://aleung.github.io/blog/2012/10/06/change-locale-in-android-application/ 98 | private static void loadLocale(String language) { 99 | Configuration config = new Configuration(); 100 | config.locale = getLocale(language); 101 | context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics()); 102 | } 103 | 104 | public static String getVersion() { 105 | try { 106 | return context.getPackageManager() 107 | .getPackageInfo(context.getPackageName(), 0).versionName; 108 | } 109 | catch (PackageManager.NameNotFoundException e) { 110 | e.printStackTrace(); 111 | return ""; 112 | } 113 | } 114 | 115 | public static void onSharedPreferenceChanged(SharedPreferences pref, String s) { 116 | if (s.equals(context.getString(R.string.pref_key_language))) { 117 | loadLocale(pref.getString(s, "")); 118 | } 119 | else if (s.equals(context.getString(R.string.pref_key_web_cache_size))) { 120 | volleyCache = new DiskBasedCache(new File(context.getCacheDir(), "NyasamaVolleyCache"), 121 | 1024 * 1024 * Helper.toSafeInteger(pref.getString(s, ""), 32)); 122 | } 123 | else if (s.equals("disk_cache")) { 124 | int size = Helper.toSafeInteger(pref.getString(s, ""), 256); 125 | try { 126 | if (size > 0) 127 | fileDiskCache = DiskLruCache.open(new File(context.getExternalCacheDir(), "file-cache"), 128 | 0, 1, 1024 * 1024 * size); 129 | } 130 | catch (IOException e) { 131 | Helper.toast(e.getMessage()); 132 | System.exit(1); 133 | } 134 | } 135 | } 136 | 137 | @Override 138 | public void onCreate() { 139 | super.onCreate(); 140 | context = getApplicationContext(); 141 | 142 | ACRA.init(this); 143 | 144 | // load preferences 145 | preferences = PreferenceManager.getDefaultSharedPreferences(context); 146 | ThisApp.onSharedPreferenceChanged(preferences, getString(R.string.pref_key_language)); 147 | ThisApp.onSharedPreferenceChanged(preferences, getString(R.string.pref_key_web_cache_size)); 148 | ThisApp.onSharedPreferenceChanged(preferences, getString(R.string.pref_key_manga_cache_size)); 149 | 150 | // REF: http://stackoverflow.com/questions/18786059/change-redirect-policy-of-volley-framework 151 | //创建 HTML 请求队列 152 | Network network = new BasicNetwork(new HurlStack() { 153 | @Override 154 | protected HttpURLConnection createConnection(URL url) throws IOException { 155 | HttpURLConnection connection = super.createConnection(url); 156 | if (url.getRef() != null && url.getRef().contains("#hurlstack:noredirect#")) 157 | connection.setInstanceFollowRedirects(false); 158 | return connection; 159 | } 160 | }); 161 | requestQueue = new RequestQueue(volleyCache, network); 162 | requestQueue.start(); 163 | 164 | //创建 imageLoader ,与请求队列绑定,并使用 BitmapLruCache 存储器 (见util) 165 | ImageLoader.ImageCache imgCache = new BitmapLruCache(); 166 | imageLoader = new ImageLoader(requestQueue, imgCache); 167 | //初始化webView 和 cookieStore 168 | cookieStore = new PersistenceCookieStore(context); 169 | CookieHandler.setDefault(new CookieManager(cookieStore, CookiePolicy.ACCEPT_ALL)); 170 | 171 | webView = new WebView(context); 172 | webView.getSettings().setJavaScriptEnabled(true); 173 | } 174 | 175 | /* 176 | restart 函数设定为全局函数,更改语言后调用(或出现内部错误调用) 177 | alarmmanager 在0.1秒后激发 pendingintent,pendingintent 是一种自带操作的intent 178 | 这里的操作是启动一个activity (getactivities) 179 | 启动哪个 activity 则是由 pendingintent 中的另一个intent决定,这里启动的activity 是 splashactivity 180 | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK 181 | 这3个flag 确保启动的 activity 是task中的第一个 activty 182 | exit 的参数并没有特殊含义 183 | */ 184 | public static void restart() { 185 | // REF: http://stackoverflow.com/questions/6609414/howto-programatically-restart-android-app 186 | Intent intent = new Intent(context, SplashActivity.class); 187 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK); 188 | Intent[] intents = {intent}; 189 | PendingIntent pendingIntent = PendingIntent.getActivities(ThisApp.context, 0, 190 | intents, 191 | PendingIntent.FLAG_ONE_SHOT); 192 | AlarmManager mgr = (AlarmManager) ThisApp.context.getSystemService(Context.ALARM_SERVICE); 193 | mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent); 194 | System.exit(2); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/activity/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.activity; 2 | import android.content.Intent; 3 | import android.os.Bundle; 4 | import android.app.Activity; 5 | import com.badlogic.gdx.backends.android.AndroidApplication; 6 | import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; 7 | import com.nyasama.libgdx.AboutCore; 8 | /** 9 | * Created by D.zzm on 2014.12.14. 10 | */ 11 | public class AboutActivity extends AndroidApplication { 12 | @Override 13 | protected void onCreate (Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); 16 | initialize(new AboutCore(), config); 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/activity/BaseThemedActivity.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.activity; 2 | 3 | import android.content.Intent; 4 | import android.content.res.Resources; 5 | import android.os.Bundle; 6 | import android.support.v4.app.FragmentActivity; 7 | import android.view.MenuItem; 8 | 9 | import com.negusoft.holoaccent.AccentHelper; 10 | import com.negusoft.holoaccent.AccentResources; 11 | import com.nyasama.R; 12 | import com.nyasama.ThisApp; 13 | import com.nyasama.util.Discuz; 14 | 15 | /** 16 | * Created by oxyflour on 2015/1/2. 17 | * REF: https://github.com/negusoft/holoaccent/blob/master/HoloAccentExample/src/com/negusoft/holoaccent/example/activity/AccentFragmentActivity.java 18 | */ 19 | public class BaseThemedActivity extends FragmentActivity { 20 | 21 | private final AccentHelper mAccentHelper = new AccentHelper(getOverrideAccentColor(), 22 | getOverrideAccentColorDark(), getOverrideAccentColorActionBar(), new MyInitListener()); 23 | 24 | @Override 25 | public Resources getResources() { 26 | return mAccentHelper.getResources(this, super.getResources()); 27 | } 28 | 29 | /** 30 | * Override this method to set the accent color programmatically. 31 | * @return The color to override. If the color is equals to 0, the 32 | * accent color will be taken from the theme. 33 | */ 34 | public int getOverrideAccentColor() { 35 | // Note: we have to use ThisApp context to get string, or it will fail 36 | return ThisApp.preferences.getInt(ThisApp.context.getString(R.string.pref_key_theme_color), 0x616F8B); 37 | } 38 | 39 | /** 40 | * Override this method to set the dark variant of the accent color programmatically. 41 | * @return The color to override. If the color is equals to 0, the dark version will be 42 | * taken from the theme. If it is specified in the theme either, it will be calculated 43 | * based on the accent color. 44 | */ 45 | public int getOverrideAccentColorDark() { 46 | return 0; 47 | } 48 | 49 | /** 50 | * Override this method to set the action bar variant of the accent color programmatically. 51 | * @return The color to override. If the color is equals to 0, the action bar version will 52 | * be taken from the theme. If it is specified in the theme either, it will the same as the 53 | * accent color. 54 | */ 55 | public int getOverrideAccentColorActionBar() { 56 | return 0; 57 | } 58 | 59 | /** Getter for the AccentHelper instance. */ 60 | @SuppressWarnings("unused") 61 | public AccentHelper getAccentHelper() { 62 | return mAccentHelper; 63 | } 64 | 65 | /** 66 | * Override this function to modify the AccentResources instance. You can add your own logic 67 | * to the default HoloAccent behaviour. 68 | */ 69 | @SuppressWarnings("unused") 70 | public void onInitAccentResources(AccentResources resources) { 71 | // To be overriden in child classes. 72 | } 73 | 74 | private class MyInitListener implements AccentHelper.OnInitListener { 75 | @Override 76 | public void onInitResources(AccentResources resources) { 77 | onInitAccentResources(resources); 78 | } 79 | } 80 | 81 | @Override 82 | protected void onCreate(Bundle savedInstanceState) { 83 | super.onCreate(savedInstanceState); 84 | 85 | setTheme(ThisApp.preferences.getBoolean(getString(R.string.pref_key_animation), false) ? 86 | R.style.AppThemeAni : R.style.AppTheme); 87 | 88 | if (getActionBar() != null) 89 | getActionBar().setIcon(getResources().getDrawable(R.drawable.ab_icon)); 90 | } 91 | 92 | @Override 93 | public boolean onOptionsItemSelected(MenuItem item) { 94 | int id = item.getItemId(); 95 | if (id == android.R.id.home) { 96 | finish(); 97 | return true; 98 | } 99 | else if (id == R.id.action_settings) { 100 | startActivity(new Intent(this, SettingActivity.class)); 101 | return true; 102 | } 103 | else if (id == R.id.action_my_profile) { 104 | if (Discuz.sHasLogined) 105 | startActivity(new Intent(this, UserProfileActivity.class)); 106 | else startActivityForResult(new Intent(this, LoginActivity.class), 107 | LoginActivity.REQUEST_CODE_LOGIN); 108 | return true; 109 | } 110 | return super.onOptionsItemSelected(item); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/activity/FavListActivity.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.activity; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.view.Menu; 7 | import android.view.View; 8 | 9 | import com.android.volley.Response; 10 | import com.nyasama.R; 11 | import com.nyasama.util.CommonListAdapter; 12 | import com.nyasama.fragment.CommonListFragment; 13 | import com.nyasama.util.Discuz; 14 | import com.nyasama.util.Discuz.FavItem; 15 | import com.nyasama.util.Helper; 16 | 17 | import org.json.JSONArray; 18 | import org.json.JSONException; 19 | import org.json.JSONObject; 20 | 21 | import java.util.HashMap; 22 | import java.util.List; 23 | 24 | public class FavListActivity extends BaseThemedActivity 25 | implements CommonListFragment.OnListFragmentInteraction { 26 | 27 | final static int PAGE_SIZE_COUNT = 20; 28 | public static String TAG = "FavList"; 29 | 30 | private CommonListFragment mListFragment; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_simple_framelayout); 36 | if (getActionBar() != null) 37 | getActionBar().setDisplayHomeAsUpEnabled(true); 38 | 39 | mListFragment = CommonListFragment.getNewFragment( 40 | FavItem.class, 41 | R.layout.fragment_simple_list, 42 | R.layout.fragment_fav_item, 43 | R.id.list); 44 | 45 | getSupportFragmentManager().beginTransaction() 46 | .replace(R.id.container, mListFragment).commit(); 47 | } 48 | 49 | 50 | @Override 51 | public boolean onCreateOptionsMenu(Menu menu) { 52 | // Inflate the menu; this adds items to the action bar if it is present. 53 | getMenuInflater().inflate(R.menu.menu_fav_list, menu); 54 | return true; 55 | } 56 | 57 | @Override 58 | public CommonListAdapter getListViewAdaptor(CommonListFragment fragment) { 59 | return new CommonListAdapter() { 60 | @Override 61 | public void convertView(ViewHolder viewHolder, FavItem item) { 62 | viewHolder.setText(R.id.title, item.title); 63 | viewHolder.setText(R.id.date, item.dateline); 64 | } 65 | }; 66 | } 67 | 68 | @Override 69 | public void onItemClick(CommonListFragment fragment, View view, int position, long id) { 70 | FavItem item = mListFragment.getData(position); 71 | if ("tid".equals(item.type)) { 72 | Intent intent = new Intent(this, PostListActivity.class); 73 | intent.putExtra("tid", item.dataId); 74 | intent.putExtra("title", item.title); 75 | startActivity(intent); 76 | } 77 | } 78 | 79 | @Override 80 | @SuppressWarnings("unchecked") 81 | public void onLoadingMore(CommonListFragment fragment, final List listData) { 82 | final int page = listData.size() / PAGE_SIZE_COUNT; 83 | Discuz.execute("myfavthread", new HashMap() {{ 84 | put("page", page + 1); 85 | }}, null, new Response.Listener() { 86 | @Override 87 | public void onResponse(JSONObject data) { 88 | int total = -1; 89 | if (data.has(Discuz.VOLLEY_ERROR)) { 90 | Helper.toast(R.string.network_error_toast); 91 | } else { 92 | Helper.setListLength(listData, page * PAGE_SIZE_COUNT); 93 | try { 94 | JSONObject var = data.getJSONObject("Variables"); 95 | 96 | JSONArray list = var.getJSONArray("list"); 97 | for (int i = 0; i < list.length(); i++) { 98 | listData.add(new FavItem(list.getJSONObject(i))); 99 | } 100 | 101 | total = Helper.toSafeInteger(var.getString("count"), listData.size()); 102 | 103 | } catch (JSONException e) { 104 | Log.e(TAG, "JsonError: Load PM Lists Failed (" + e.getMessage() + ")"); 105 | Helper.toast(R.string.load_failed_toast); 106 | } 107 | } 108 | mListFragment.loadMoreDone(total); 109 | } 110 | }); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/activity/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.activity; 2 | 3 | import android.content.SharedPreferences; 4 | import android.os.Bundle; 5 | import android.view.Gravity; 6 | import android.view.Menu; 7 | import android.view.View; 8 | import android.widget.AdapterView; 9 | import android.widget.ArrayAdapter; 10 | import android.widget.Button; 11 | import android.widget.EditText; 12 | import android.widget.Spinner; 13 | 14 | import com.android.volley.Response; 15 | import com.nyasama.R; 16 | import com.nyasama.util.Discuz; 17 | import com.nyasama.util.Helper; 18 | 19 | import org.json.JSONObject; 20 | 21 | public class LoginActivity extends BaseThemedActivity 22 | implements AdapterView.OnItemSelectedListener { 23 | 24 | public static final int REQUEST_CODE_LOGIN = 1; 25 | 26 | public void doLogin(View view) { 27 | final String username = mUsername.getText().toString(); 28 | final String password = mPassword.getText().toString(); 29 | final String answer = mAnswer.getText().toString(); 30 | if (username.isEmpty() || password.isEmpty()) { 31 | Helper.toast(getString(R.string.login_empty_message), Gravity.TOP, 0, 0.2); 32 | return; 33 | } 34 | 35 | Discuz.login(username, password, mQuestionId, answer, new Response.Listener() { 36 | @Override 37 | public void onResponse(JSONObject data) { 38 | if (data.has(Discuz.VOLLEY_ERROR)) { 39 | Helper.toast(R.string.network_error_toast); 40 | } 41 | else { 42 | JSONObject message = data.optJSONObject("Message"); 43 | JSONObject var = data.optJSONObject("Variables"); 44 | if (message != null) { 45 | String messageval = message.optString("messageval"); 46 | if ("login_succeed".equals(messageval) || 47 | "location_login_succeed".equals(messageval)) { 48 | // return uid to parent activity 49 | if (var != null) 50 | setResult(Helper.toSafeInteger(var.optString("member_uid"), 0)); 51 | finish(); 52 | } 53 | else 54 | Helper.toast(message.optString("messagestr"), Gravity.TOP, 0, 0.2); 55 | } 56 | 57 | SharedPreferences.Editor editor = mPrefs.edit(); 58 | editor.putString("username", username); 59 | editor.apply(); 60 | } 61 | mButton.setEnabled(true); 62 | } 63 | }); 64 | mButton.setEnabled(false); 65 | } 66 | 67 | private EditText mUsername; 68 | private EditText mPassword; 69 | private Button mButton; 70 | private Spinner mQuestion; 71 | private int mQuestionId = 0; 72 | private EditText mAnswer; 73 | private SharedPreferences mPrefs; 74 | 75 | @Override 76 | protected void onCreate(Bundle savedInstanceState) { 77 | super.onCreate(savedInstanceState); 78 | setContentView(R.layout.activity_login); 79 | if (getActionBar() != null) 80 | getActionBar().setDisplayHomeAsUpEnabled(true); 81 | 82 | mUsername = (EditText) findViewById(R.id.username); 83 | mPassword = (EditText) findViewById(R.id.password); 84 | mButton = (Button) findViewById(R.id.login_button); 85 | mQuestion = (Spinner) findViewById(R.id.question); 86 | mAnswer = (EditText) findViewById(R.id.answer); 87 | 88 | ArrayAdapter adapter = ArrayAdapter.createFromResource(this, 89 | R.array.login_question, android.R.layout.simple_spinner_item); 90 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 91 | mQuestion.setAdapter(adapter); 92 | mQuestion.setOnItemSelectedListener(this); 93 | 94 | mPrefs = getSharedPreferences("login_info", MODE_PRIVATE); 95 | mUsername.setText(mPrefs.getString("username", "")); 96 | } 97 | 98 | 99 | @Override 100 | public boolean onCreateOptionsMenu(Menu menu) { 101 | // Inflate the menu; this adds items to the action bar if it is present. 102 | getMenuInflater().inflate(R.menu.menu_login, menu); 103 | return true; 104 | } 105 | 106 | @Override 107 | public void onItemSelected(AdapterView adapterView, View view, int i, long l) { 108 | mQuestionId = i; 109 | Helper.updateVisibility(mAnswer, mQuestionId > 0); 110 | } 111 | 112 | @Override 113 | public void onNothingSelected(AdapterView adapterView) { 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/activity/PMListActivity.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.activity; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.view.Menu; 7 | import android.view.View; 8 | 9 | import com.android.volley.Response; 10 | import com.android.volley.toolbox.NetworkImageView; 11 | import com.nyasama.R; 12 | import com.nyasama.ThisApp; 13 | import com.nyasama.util.CommonListAdapter; 14 | import com.nyasama.fragment.CommonListFragment; 15 | import com.nyasama.util.Discuz; 16 | import com.nyasama.util.Discuz.PMList; 17 | import com.nyasama.util.Helper; 18 | 19 | import org.json.JSONArray; 20 | import org.json.JSONException; 21 | import org.json.JSONObject; 22 | 23 | import java.util.HashMap; 24 | import java.util.List; 25 | 26 | public class PMListActivity extends BaseThemedActivity 27 | implements CommonListFragment.OnListFragmentInteraction { 28 | 29 | public static int PAGE_SIZE_COUNT = 20; 30 | public static String TAG = "PMList"; 31 | 32 | private CommonListFragment mListFragment; 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_simple_framelayout); 38 | if (getActionBar() != null) 39 | getActionBar().setDisplayHomeAsUpEnabled(true); 40 | 41 | mListFragment = CommonListFragment.getNewFragment( 42 | PMList.class, 43 | R.layout.fragment_simple_list, 44 | R.layout.fragment_pmlist_item, 45 | R.id.list); 46 | 47 | getSupportFragmentManager().beginTransaction() 48 | .replace(R.id.container, mListFragment).commit(); 49 | 50 | } 51 | 52 | 53 | @Override 54 | public boolean onCreateOptionsMenu(Menu menu) { 55 | // Inflate the menu; this adds items to the action bar if it is present. 56 | getMenuInflater().inflate(R.menu.menu_pm_list, menu); 57 | return true; 58 | } 59 | 60 | @Override 61 | public CommonListAdapter getListViewAdaptor(CommonListFragment fragment) { 62 | return new CommonListAdapter() { 63 | @Override 64 | public void convertView(ViewHolder viewHolder, PMList item) { 65 | int uid = item.fromUserId != Discuz.sUid ? item.fromUserId : item.toUserId; 66 | String username = item.fromUserId != Discuz.sUid ? item.fromUser : item.toUser; 67 | 68 | String avatar_url = Discuz.DISCUZ_URL + 69 | "uc_server/avatar.php?uid=" + uid + "&size=small"; 70 | ((NetworkImageView) viewHolder.getView(R.id.avatar)) 71 | .setImageUrl(avatar_url, ThisApp.imageLoader); 72 | 73 | viewHolder.setText(R.id.author, username + " (" + item.number + ")"); 74 | viewHolder.setText(R.id.message, item.message); 75 | viewHolder.setText(R.id.date, item.lastdate); 76 | } 77 | }; 78 | } 79 | 80 | @Override 81 | public void onItemClick(CommonListFragment fragment, View view, int position, long id) { 82 | Intent intent = new Intent(this, MessagesActivity.class); 83 | intent.putExtra("touid", ((PMList) fragment.getData(position)).toUserId); 84 | startActivity(intent); 85 | } 86 | 87 | @Override 88 | @SuppressWarnings("unchecked") 89 | public void onLoadingMore(CommonListFragment fragment, final List listData) { 90 | final int page = listData.size() / PAGE_SIZE_COUNT; 91 | Discuz.execute("mypm", new HashMap() {{ 92 | put("page", page + 1); 93 | }}, null, new Response.Listener() { 94 | @Override 95 | public void onResponse(JSONObject data) { 96 | int total = -1; 97 | if (data.has(Discuz.VOLLEY_ERROR)) { 98 | Helper.toast(R.string.network_error_toast); 99 | } 100 | else { 101 | Helper.setListLength(listData, page * PAGE_SIZE_COUNT); 102 | try { 103 | JSONObject var = data.getJSONObject("Variables"); 104 | 105 | JSONArray list = var.getJSONArray("list"); 106 | for (int i = 0; i < list.length(); i ++) { 107 | listData.add(new PMList(list.getJSONObject(i))); 108 | } 109 | 110 | total = Helper.toSafeInteger(var.getString("count"), listData.size()); 111 | 112 | } catch (JSONException e) { 113 | Log.e(TAG, "JsonError: Load PM Lists Failed (" + e.getMessage() + ")"); 114 | Helper.toast(R.string.load_failed_toast); 115 | } 116 | } 117 | mListFragment.loadMoreDone(total); 118 | } 119 | }); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/activity/SearchActivity.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.activity; 2 | 3 | import android.app.SearchManager; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.text.Html; 7 | import android.view.Menu; 8 | import android.view.View; 9 | import android.widget.SearchView; 10 | 11 | import com.android.volley.Response; 12 | import com.negusoft.holoaccent.dialog.AccentAlertDialog; 13 | import com.nyasama.R; 14 | import com.nyasama.fragment.CommonListFragment; 15 | import com.nyasama.util.CommonListAdapter; 16 | import com.nyasama.util.Discuz; 17 | import com.nyasama.util.Helper; 18 | import com.nyasama.util.Discuz.Thread; 19 | 20 | import org.json.JSONObject; 21 | 22 | import java.util.HashMap; 23 | import java.util.Iterator; 24 | import java.util.List; 25 | 26 | public class SearchActivity extends BaseThemedActivity 27 | implements CommonListFragment.OnListFragmentInteraction { 28 | 29 | private static final int PAGE_SIZE_COUNT = 25; 30 | private CommonListFragment mListFragment; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_simple_framelayout); 36 | if (getActionBar() != null) 37 | getActionBar().setDisplayHomeAsUpEnabled(true); 38 | 39 | mListFragment = CommonListFragment.getNewFragment( 40 | Object.class, 41 | R.layout.fragment_simple_list, 42 | R.layout.fragment_thread_item, 43 | R.id.list); 44 | 45 | getSupportFragmentManager().beginTransaction() 46 | .replace(R.id.container, mListFragment) 47 | .commit(); 48 | } 49 | 50 | 51 | @Override 52 | public boolean onCreateOptionsMenu(Menu menu) { 53 | // Inflate the menu; this adds items to the action bar if it is present. 54 | getMenuInflater().inflate(R.menu.menu_search, menu); 55 | 56 | SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView(); 57 | searchView.setIconified(false); 58 | 59 | String query = getIntent().getStringExtra(SearchManager.QUERY); 60 | if (query != null) searchView.setQuery(query, false); 61 | 62 | searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { 63 | @Override 64 | public boolean onQueryTextSubmit(String s) { 65 | getIntent().putExtra(SearchManager.QUERY, s); 66 | if (mListFragment != null) 67 | mListFragment.reloadAll(); 68 | return false; 69 | } 70 | @Override 71 | public boolean onQueryTextChange(String s) { 72 | return false; 73 | } 74 | }); 75 | 76 | return true; 77 | } 78 | 79 | @Override 80 | public CommonListAdapter getListViewAdaptor(CommonListFragment fragment) { 81 | return new CommonListAdapter() { 82 | @Override 83 | public void convertView(ViewHolder viewHolder, Object obj) { 84 | Thread item = (Thread) obj; 85 | viewHolder.setText(R.id.title, Html.fromHtml(item.title)); 86 | viewHolder.setText(R.id.sub, 87 | Html.fromHtml(item.author + " " + item.lastpost)); 88 | viewHolder.setText(R.id.inf, item.replies + "/" + item.views); 89 | } 90 | }; 91 | } 92 | 93 | @Override 94 | public void onItemClick(CommonListFragment fragment, View view, int position, long id) { 95 | Object obj = fragment.getData(position); 96 | if (obj instanceof Thread) { 97 | Thread thread = (Thread) obj; 98 | Intent intent = new Intent(this, PostListActivity.class); 99 | intent.putExtra("tid", thread.id); 100 | intent.putExtra("title", Html.fromHtml(thread.title).toString()); 101 | startActivity(intent); 102 | } 103 | } 104 | 105 | @Override 106 | @SuppressWarnings("unchecked") 107 | public void onLoadingMore(final CommonListFragment fragment, final List listData) { 108 | String query = getIntent().getStringExtra(SearchManager.QUERY); 109 | if (query == null || query.isEmpty()) { 110 | fragment.loadMoreDone(0); 111 | return; 112 | } 113 | final int page = listData.size() / PAGE_SIZE_COUNT; 114 | Discuz.search(query, new HashMap() {{ 115 | put("page", page + 1); 116 | put("tpp", PAGE_SIZE_COUNT); 117 | }}, new Response.Listener() { 118 | @Override 119 | public void onResponse(JSONObject data) { 120 | int total = -1; 121 | if (data.has(Discuz.VOLLEY_ERROR)) { 122 | Helper.toast(R.string.there_is_something_wrong); 123 | } else if (data.has("Message")) { 124 | new AccentAlertDialog.Builder(SearchActivity.this) 125 | .setTitle(R.string.there_is_something_wrong) 126 | .setMessage(data.optJSONObject("Message").optString("messagestr")) 127 | .setPositiveButton(android.R.string.ok, null) 128 | .show(); 129 | } else if (data.has("Variables")) { 130 | JSONObject var = data.optJSONObject("Variables"); 131 | Helper.setListLength(listData, page * PAGE_SIZE_COUNT); 132 | // TODO: update search.php and parse more results here 133 | JSONObject list = var.optJSONObject("threadlist"); 134 | if (list != null) for (Iterator iter = list.keys(); iter.hasNext(); ) { 135 | String key = iter.next(); 136 | listData.add(new Thread(list.optJSONObject(key))); 137 | } 138 | // update total 139 | total = Helper.toSafeInteger(var.optString("count"), listData.size()); 140 | } 141 | fragment.loadMoreDone(total); 142 | } 143 | }); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/activity/SettingActivity.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.activity; 2 | 3 | import android.content.DialogInterface; 4 | import android.content.SharedPreferences; 5 | import android.os.Bundle; 6 | import android.preference.PreferenceFragment; 7 | 8 | import com.negusoft.holoaccent.dialog.AccentAlertDialog; 9 | import com.nyasama.R; 10 | import com.nyasama.ThisApp; 11 | 12 | // REF: http://blog.csdn.net/lincyang/article/details/20609673 13 | // REF: https://github.com/negusoft/holoaccent/wiki/Preferences-Activity 14 | 15 | //设置activity 16 | // 17 | //设置activity 和普通activity区别不大,只是layout文件的格式略不一样 18 | //详细文档见:http://developer.android.com/guide/topics/ui/settings.html 19 | //有两个建议:1, 添加 PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false); 20 | // 见详细文档 Setting Default Values 节,虽然感觉上不加这句也没问题,但还是加上安心 21 | // 2,明确创建出 OnSharedPreferenceChangeListener 实例 22 | // 见详细文档Reading Preference 节,Listening for preference changes 块的最后部分,同样是加上更安心 23 | public class SettingActivity extends BaseThemedActivity { 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | if (savedInstanceState == null) { 29 | getFragmentManager().beginTransaction() 30 | .replace(android.R.id.content, new ThisPreferenceFragment()) 31 | .commit(); 32 | } 33 | } 34 | 35 | public static class ThisPreferenceFragment extends PreferenceFragment 36 | implements SharedPreferences.OnSharedPreferenceChangeListener { 37 | @Override 38 | public void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | addPreferencesFromResource(R.xml.preference); 41 | 42 | getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); 43 | // update version string 44 | findPreference(getString(R.string.pref_key_version)).setSummary(ThisApp.getVersion()); 45 | // update setting 46 | onSharedPreferenceChanged(getPreferenceScreen().getSharedPreferences(), 47 | getString(R.string.pref_key_show_image)); 48 | } 49 | 50 | @Override 51 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 52 | if (getActivity() == null) return; 53 | 54 | ThisApp.onSharedPreferenceChanged(sharedPreferences, key); 55 | if (key.equals(getString(R.string.pref_key_language))) { 56 | if (!getActivity().isFinishing()) new AccentAlertDialog.Builder(getActivity()) 57 | .setTitle(getString(R.string.alert_need_reboot)) 58 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 59 | @Override 60 | public void onClick(DialogInterface dialogInterface, int i) { 61 | ThisApp.restart(); 62 | } 63 | }) 64 | .setNegativeButton(android.R.string.cancel, null) 65 | .show(); 66 | } 67 | else if (key.equals(getString(R.string.pref_key_show_image))) { 68 | findPreference(getString(R.string.pref_key_thumb_size)) 69 | .setEnabled(!"false".equals(sharedPreferences.getString(key, ""))); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/activity/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.activity; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.net.Uri; 8 | import android.os.Bundle; 9 | import android.text.Html; 10 | import android.util.Log; 11 | import android.widget.TextView; 12 | 13 | import com.android.volley.NetworkResponse; 14 | import com.android.volley.ParseError; 15 | import com.android.volley.Response; 16 | import com.android.volley.VolleyError; 17 | import com.android.volley.toolbox.HttpHeaderParser; 18 | import com.android.volley.toolbox.StringRequest; 19 | import com.nyasama.R; 20 | import com.nyasama.ThisApp; 21 | 22 | import java.io.UnsupportedEncodingException; 23 | 24 | /** 25 | * Created by oxyflour on 2014/11/23. 26 | * 27 | */ 28 | public class SplashActivity extends Activity { 29 | 30 | public static final String releaseUrl = "http://dev.nyasama.com/release"; 31 | 32 | public static class UTF8StringRequest extends StringRequest { 33 | 34 | public UTF8StringRequest(String url, Response.Listener listener, Response.ErrorListener errorListener) { 35 | super(url, listener, errorListener); 36 | } 37 | 38 | @Override 39 | protected Response parseNetworkResponse(NetworkResponse response) { 40 | try { 41 | return Response.success(new String(response.data, "utf8"), 42 | HttpHeaderParser.parseCacheHeaders(response)); 43 | } 44 | catch (UnsupportedEncodingException e) { 45 | return Response.error(new ParseError(e)); 46 | } 47 | } 48 | } 49 | 50 | int mInitJobs = 0; 51 | void checkInitJobs() { 52 | mInitJobs --; 53 | if (mInitJobs <= 0) 54 | onInitDone(); 55 | } 56 | 57 | String mVersion = ""; 58 | String mUpdateMessage = ""; 59 | 60 | void onInitDone() { 61 | final String versionName = ThisApp.getVersion(); 62 | if (!mVersion.isEmpty() && !mVersion.equals(versionName)) { 63 | TextView messageText = new TextView(SplashActivity.this); 64 | messageText.setPadding(32, 32, 32, 32); 65 | messageText.setText(Html.fromHtml( 66 | "" + String.format(getString(R.string.new_version_alert_message), mVersion) + "" + 67 | (mUpdateMessage.isEmpty() ? "" : "

"+ 68 | getString(R.string.new_feature) + "
"+ 69 | mUpdateMessage.replace("\n", "
")) 70 | )); 71 | new AlertDialog.Builder(SplashActivity.this) 72 | .setTitle(getString(R.string.new_version_alert_title)) 73 | .setView(messageText) 74 | .setCancelable(false) 75 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 76 | @Override 77 | public void onClick(DialogInterface dialogInterface, int i) { 78 | startActivity(new Intent(Intent.ACTION_VIEW, 79 | Uri.parse(releaseUrl + "/dlapk.php?v=" + mVersion))); 80 | finish(); 81 | } 82 | }) 83 | .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 84 | @Override 85 | public void onClick(DialogInterface dialogInterface, int i) { 86 | startActivity(new Intent(SplashActivity.this, MainActivity.class)); 87 | finish(); 88 | } 89 | }) 90 | .setNeutralButton(R.string.do_not_remind, new DialogInterface.OnClickListener() { 91 | @Override 92 | public void onClick(DialogInterface dialogInterface, int i) { 93 | ThisApp.preferences.edit().putBoolean(getString(R.string.pref_key_check_update), false).commit(); 94 | startActivity(new Intent(SplashActivity.this, MainActivity.class)); 95 | finish(); 96 | } 97 | }) 98 | .show(); 99 | } 100 | else { 101 | Log.e(SplashActivity.class.toString(), "check update failed: no new version found"); 102 | startActivity(new Intent(SplashActivity.this, MainActivity.class)); 103 | finish(); 104 | } 105 | } 106 | 107 | @Override 108 | protected void onCreate(Bundle savedInstanceState) { 109 | super.onCreate(savedInstanceState); 110 | setContentView(R.layout.activity_splash_screen); 111 | 112 | mInitJobs ++; 113 | boolean checkUpdate = ThisApp.preferences.getBoolean(getString(R.string.pref_key_check_update), true); 114 | if (!checkUpdate) new android.os.Handler().post(new Runnable() { 115 | @Override 116 | public void run() { 117 | Log.e(SplashActivity.class.toString(), 118 | "we are not checking for updates."); 119 | checkInitJobs(); 120 | } 121 | }); 122 | else ThisApp.requestQueue.add(new UTF8StringRequest(releaseUrl + "/version_and_feature.txt", new Response.Listener() { 123 | @Override 124 | public void onResponse(String s) { 125 | if (s != null) { 126 | s = s.replace("\uFEFF", "").replace("\r\n", "\n"); 127 | int i = s.indexOf('\n'); 128 | if (i >= 0) { 129 | mVersion = s.substring(0, i); 130 | mUpdateMessage = s.substring(i + 1); 131 | } 132 | else { 133 | mVersion = s; 134 | } 135 | } 136 | checkInitJobs(); 137 | } 138 | }, new Response.ErrorListener() { 139 | @Override 140 | public void onErrorResponse(VolleyError volleyError) { 141 | Log.e(SplashActivity.class.toString(), volleyError.getMessage() != null ? 142 | volleyError.getMessage() : "Unknown"); 143 | checkInitJobs(); 144 | } 145 | })); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/fragment/CommonListFragment.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.fragment; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.widget.SwipeRefreshLayout; 7 | import android.util.Log; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.AbsListView; 12 | import android.widget.AdapterView; 13 | import android.widget.Button; 14 | import android.widget.ListView; 15 | 16 | import com.etsy.android.grid.StaggeredGridView; 17 | import com.nyasama.R; 18 | import com.nyasama.util.CommonListAdapter; 19 | import com.nyasama.util.Helper; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | /** 25 | * Created by oxyflour on 2014/11/18. 26 | * 27 | */ 28 | public class CommonListFragment extends Fragment 29 | implements AbsListView.OnScrollListener { 30 | 31 | public static final String ARG_LIST_LAYOUT = "list_layout_id"; 32 | public static final String ARG_LIST_VIEW_ID = "list_view_id"; 33 | public static final String ARG_ITEM_LAYOUT = "item_layout_id"; 34 | 35 | private List mListData = new ArrayList(); 36 | private int mListItemCount = Integer.MAX_VALUE; 37 | private boolean mIsLoading = false; 38 | private boolean mHasError = false; 39 | 40 | private CommonListAdapter mListAdapter; 41 | private OnListFragmentInteraction mInterface; 42 | private AbsListView.OnScrollListener mScrollListener; 43 | 44 | private View mListLayoutView; 45 | private SwipeRefreshLayout mRefreshLayoutView; 46 | 47 | @SuppressWarnings("unchecked unused") 48 | public static CommonListFragment getNewFragment(Class c, int listLayout, int itemLayout, int listViewId) { 49 | Bundle bundle = new Bundle(); 50 | bundle.putInt(CommonListFragment.ARG_LIST_LAYOUT, listLayout); 51 | bundle.putInt(CommonListFragment.ARG_ITEM_LAYOUT, itemLayout); 52 | bundle.putInt(CommonListFragment.ARG_LIST_VIEW_ID, listViewId); 53 | CommonListFragment fragment = new CommonListFragment(); 54 | fragment.setArguments(bundle); 55 | return fragment; 56 | } 57 | 58 | public boolean loadMore() { 59 | final int currentSize = mListData.size(); 60 | if (mListLayoutView != null && 61 | currentSize < mListItemCount && !mIsLoading) { 62 | Helper.updateVisibility(mListLayoutView, R.id.empty, false); 63 | Helper.updateVisibility(mListLayoutView, R.id.loading, mIsLoading = true); 64 | Helper.updateVisibility(mListLayoutView, R.id.error, mHasError = false); 65 | if (mInterface != null) 66 | mInterface.onLoadingMore(this, mListData); 67 | if (mRefreshLayoutView != null && mRefreshLayoutView.isRefreshing()) 68 | Helper.updateVisibility(mListLayoutView, R.id.loading, false); 69 | } 70 | return mIsLoading; 71 | } 72 | 73 | public void loadMoreDone(int total) { 74 | mListItemCount = total; 75 | if (mHasError = total < 0) { 76 | mListData.clear(); 77 | mListItemCount = 0; 78 | Log.e("ListFragment", "load failed"); 79 | } 80 | 81 | mIsLoading = false; 82 | mListAdapter.notifyDataSetChanged(); 83 | if (mListLayoutView != null) { 84 | Helper.updateVisibility(mListLayoutView, R.id.empty, mListItemCount == 0 && !mHasError); 85 | Helper.updateVisibility(mListLayoutView, R.id.error, mHasError); 86 | Helper.updateVisibility(mListLayoutView, R.id.loading, mListItemCount > mListData.size()); 87 | } 88 | if (mRefreshLayoutView != null) { 89 | mRefreshLayoutView.setRefreshing(false); 90 | } 91 | } 92 | 93 | public boolean reloadAll() { 94 | mListData.clear(); 95 | mListAdapter.notifyDataSetChanged(); 96 | mListItemCount = Integer.MAX_VALUE; 97 | return loadMore(); 98 | } 99 | 100 | public boolean reloadLast() { 101 | mListItemCount = Integer.MAX_VALUE; 102 | return loadMore(); 103 | } 104 | 105 | public void setOnScrollListener(AbsListView.OnScrollListener onScrollListener) { 106 | mScrollListener = onScrollListener; 107 | } 108 | 109 | public T getData(int position) { 110 | return mListData.get(position); 111 | } 112 | 113 | public int getIndex(T data) { 114 | return mListData.indexOf(data); 115 | } 116 | 117 | public CommonListAdapter getListAdapter() { 118 | return mListAdapter; 119 | } 120 | 121 | @Override 122 | @SuppressWarnings("unchecked") 123 | public void onAttach(Activity activity) { 124 | super.onAttach(activity); 125 | if (this instanceof OnListFragmentInteraction) 126 | mInterface = (OnListFragmentInteraction) this; 127 | else if (activity instanceof OnListFragmentInteraction) 128 | mInterface = (OnListFragmentInteraction) activity; 129 | else 130 | throw new RuntimeException("you should implement OnListFragmentInteraction on activity or subclass"); 131 | loadMore(); 132 | } 133 | 134 | @Override 135 | @SuppressWarnings("unchecked") 136 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 137 | Bundle bundle = getArguments() != null ? getArguments() : new Bundle(); 138 | mListLayoutView = inflater.inflate(bundle.getInt(ARG_LIST_LAYOUT, R.layout.fragment_simple_list), container, false); 139 | 140 | AbsListView listView = (AbsListView) mListLayoutView.findViewById(bundle.getInt(ARG_LIST_VIEW_ID, R.id.list)); 141 | if (listView instanceof ListView) { 142 | View loading = inflater.inflate(R.layout.fragment_simple_list_loading, listView, false); 143 | ((ListView) listView).addFooterView(loading, null, false); 144 | } 145 | else if (listView instanceof StaggeredGridView) { 146 | View loading = inflater.inflate(R.layout.fragment_simple_list_loading, listView, false); 147 | ((StaggeredGridView) listView).addFooterView(loading, null, false); 148 | } 149 | 150 | // setup list view 151 | mListAdapter = mInterface.getListViewAdaptor(this); 152 | mListAdapter.setup(mListData, bundle.getInt(ARG_ITEM_LAYOUT, android.R.layout.simple_list_item_1)); 153 | listView.setAdapter(mListAdapter); 154 | listView.setOnScrollListener(this); 155 | listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 156 | @Override 157 | public void onItemClick(AdapterView adapterView, View view, int i, long l) { 158 | mInterface.onItemClick(CommonListFragment.this, view, i, l); 159 | } 160 | }); 161 | 162 | // setup reload button 163 | Button reloadButton = (Button) mListLayoutView.findViewById(R.id.reload); 164 | if (reloadButton != null) reloadButton.setOnClickListener(new View.OnClickListener() { 165 | @Override 166 | public void onClick(View view) { 167 | reloadAll(); 168 | } 169 | }); 170 | 171 | // setup refresh layout 172 | mRefreshLayoutView = (SwipeRefreshLayout) mListLayoutView.findViewById(R.id.swipe_refresh); 173 | mRefreshLayoutView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 174 | @Override 175 | public void onRefresh() { 176 | if (!mIsLoading) 177 | reloadAll(); 178 | } 179 | }); 180 | 181 | return mListLayoutView; 182 | } 183 | 184 | @Override 185 | public void onScrollStateChanged(AbsListView absListView, int i) { 186 | if (mScrollListener != null) 187 | mScrollListener.onScrollStateChanged(absListView, i); 188 | } 189 | 190 | @Override 191 | public void onScroll(AbsListView absListView, int i, int i2, int i3) { 192 | if (i + i2 >= i3) 193 | loadMore(); 194 | if (mRefreshLayoutView != null) 195 | mRefreshLayoutView.setEnabled(i == 0); 196 | if (mScrollListener != null) 197 | mScrollListener.onScroll(absListView, i, i2, i3); 198 | } 199 | 200 | @SuppressWarnings("unused") 201 | public interface OnListFragmentInteraction { 202 | public CommonListAdapter getListViewAdaptor(CommonListFragment fragment); 203 | public void onItemClick(CommonListFragment fragment, 204 | View view, int position, long id); 205 | public void onLoadingMore(CommonListFragment fragment, List data); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/fragment/DiscuzComicListFragment.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.text.Html; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.android.volley.toolbox.NetworkImageView; 10 | import com.nyasama.R; 11 | import com.nyasama.ThisApp; 12 | import com.nyasama.util.CommonListAdapter; 13 | import com.nyasama.util.Discuz; 14 | import com.nyasama.util.Discuz.Thread; 15 | 16 | /** 17 | * Created by oxyflour on 2014/12/21. 18 | * 19 | */ 20 | public class DiscuzComicListFragment extends DiscuzThreadListFragment { 21 | 22 | public static DiscuzComicListFragment getNewFragment() { 23 | DiscuzComicListFragment fragment = new DiscuzComicListFragment(); 24 | Bundle bundle = new Bundle(); 25 | bundle.putInt(ARG_LIST_LAYOUT, R.layout.fragment_simple_grid); 26 | bundle.putInt(ARG_ITEM_LAYOUT, R.layout.fragment_thread_grid); 27 | bundle.putInt("fid", 3); 28 | fragment.setArguments(bundle); 29 | return fragment; 30 | } 31 | 32 | @Override 33 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 34 | View view = super.onCreateView(inflater, container, savedInstanceState); 35 | // set background color 36 | view.setBackgroundColor(getResources().getColor(R.color.background_light_gray)); 37 | return view; 38 | } 39 | 40 | @Override 41 | public CommonListAdapter getListViewAdaptor(CommonListFragment fragment) { 42 | return new CommonListAdapter() { 43 | @Override 44 | public void convertView(ViewHolder viewHolder, final Thread item) { 45 | viewHolder.setText(R.id.title, Html.fromHtml(item.title)); 46 | final NetworkImageView imageView = (NetworkImageView) viewHolder.getView(R.id.image_view); 47 | imageView.setDefaultImageResId(R.drawable.ic_launcher); 48 | imageView.setErrorImageResId(R.drawable.ic_launcher); 49 | imageView.setImageUrl(Discuz.getThreadCoverThumb(item.id), ThisApp.imageLoader); 50 | } 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/fragment/DiscuzForumIndexFragment.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.fragment; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ListView; 10 | import android.widget.TextView; 11 | 12 | import com.android.volley.Response; 13 | import com.android.volley.toolbox.NetworkImageView; 14 | import com.nyasama.R; 15 | import com.nyasama.ThisApp; 16 | import com.nyasama.activity.ThreadListActivity; 17 | import com.nyasama.util.CommonListAdapter; 18 | import com.nyasama.util.Discuz; 19 | import com.nyasama.util.Discuz.Forum; 20 | import com.nyasama.util.Discuz.ForumCatalog; 21 | import com.nyasama.util.Helper; 22 | 23 | import org.json.JSONArray; 24 | import org.json.JSONException; 25 | import org.json.JSONObject; 26 | 27 | import java.util.HashMap; 28 | import java.util.List; 29 | 30 | /** 31 | * Created by oxyflour on 2014/11/18. 32 | * 33 | */ 34 | public class DiscuzForumIndexFragment extends CommonListFragment 35 | implements CommonListFragment.OnListFragmentInteraction { 36 | 37 | @Override 38 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 39 | View view = super.onCreateView(inflater, container, savedInstanceState); 40 | // hide the divider 41 | ((ListView) view.findViewById(R.id.list)).setDividerHeight(0); 42 | // set background color 43 | view.setBackgroundColor(getResources().getColor(R.color.background_light_gray)); 44 | return view; 45 | } 46 | 47 | @Override 48 | public CommonListAdapter getListViewAdaptor(CommonListFragment fragment) { 49 | return new CommonListAdapter() { 50 | 51 | @Override 52 | public int getViewTypeCount() { 53 | return 2; 54 | } 55 | 56 | @Override 57 | public int getItemViewType(int position) { 58 | Object obj = DiscuzForumIndexFragment.this.getData(position); 59 | return obj instanceof ForumCatalog ? 0 : 1; 60 | } 61 | 62 | @Override 63 | public View createView(ViewGroup parent, int position) { 64 | Object obj = DiscuzForumIndexFragment.this.getData(position); 65 | int layout = obj instanceof ForumCatalog ? 66 | R.layout.fragment_forum_cat_item : 67 | R.layout.fragment_forum_item; 68 | return LayoutInflater.from(parent.getContext()) 69 | .inflate(layout, parent, false); 70 | } 71 | 72 | @Override 73 | public void convertView(ViewHolder viewHolder, Object obj) { 74 | if (obj instanceof ForumCatalog) { 75 | ForumCatalog item = (ForumCatalog) obj; 76 | ((TextView) viewHolder.getConvertView()).setText(item.name); 77 | } 78 | else { 79 | Forum item = (Forum) obj; 80 | viewHolder.setText(R.id.title, item.name); 81 | viewHolder.setText(R.id.sub, 82 | getString(R.string.forum_index_threads)+":"+item.threads+" "+ 83 | getString(R.string.forum_index_posts)+":"+item.todayPosts); 84 | NetworkImageView icon = ((NetworkImageView) viewHolder.getView(R.id.icon)); 85 | icon.setDefaultImageResId(R.drawable.default_board_icon); 86 | icon.setImageUrl(item.icon, ThisApp.imageLoader); 87 | } 88 | } 89 | }; 90 | } 91 | 92 | @Override 93 | public void onItemClick(CommonListFragment fragment, View view, int position, long id) { 94 | Object obj = getData(position); 95 | if (obj instanceof Forum) { 96 | Forum item = (Forum) obj; 97 | Intent intent = new Intent(view.getContext(), ThreadListActivity.class); 98 | intent.putExtra("fid", item.id); 99 | intent.putExtra("title", item.name); 100 | startActivity(intent); 101 | } 102 | } 103 | 104 | @Override 105 | @SuppressWarnings("unchecked") 106 | public void onLoadingMore(CommonListFragment fragment, final List listData) { 107 | Discuz.execute("forumindex", new HashMap(), null, new Response.Listener() { 108 | @Override 109 | public void onResponse(JSONObject data) { 110 | int total = -1; 111 | if (data.has(Discuz.VOLLEY_ERROR)) { 112 | Helper.toast(R.string.network_error_toast); 113 | } else if (!data.isNull("Variables")) { 114 | try { 115 | JSONObject var = data.getJSONObject("Variables"); 116 | JSONArray forumlist = var.getJSONArray("forumlist"); 117 | final JSONObject forums = new JSONObject(); 118 | for (int i = 0; i < forumlist.length(); i++) { 119 | JSONObject forum = forumlist.getJSONObject(i); 120 | forums.put(forum.getString("fid"), forum); 121 | } 122 | 123 | JSONArray catlist = var.getJSONArray("catlist"); 124 | listData.clear(); 125 | for (int i = 0; i < catlist.length(); i++) { 126 | JSONObject cat = catlist.getJSONObject(i); 127 | ForumCatalog forumCatalog = new ForumCatalog(cat.getString("name")); 128 | listData.add(forumCatalog); 129 | 130 | final JSONArray forumIds = cat.getJSONArray("forums"); 131 | for (int j = 0; j < forumIds.length(); j++) { 132 | String id = forumIds.getString(j); 133 | JSONObject forum = forums.getJSONObject(id); 134 | listData.add(new Forum(forum)); 135 | } 136 | } 137 | total = listData.size(); 138 | } catch (JSONException e) { 139 | Log.d("Discuz", "Load Forum Index Failed (" + e.getMessage() + ")"); 140 | } 141 | } 142 | loadMoreDone(total); 143 | } 144 | }); 145 | 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/fragment/DiscuzTopListFragment.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | 6 | import com.android.volley.Response; 7 | import com.nyasama.R; 8 | import com.nyasama.util.Discuz; 9 | import com.nyasama.util.Helper; 10 | import com.nyasama.util.Discuz.Thread; 11 | 12 | import org.json.JSONArray; 13 | import org.json.JSONException; 14 | import org.json.JSONObject; 15 | 16 | import java.util.HashMap; 17 | import java.util.List; 18 | 19 | /** 20 | * Created by oxyflour on 2014/11/18. 21 | * 22 | */ 23 | public class DiscuzTopListFragment extends DiscuzThreadListFragment { 24 | 25 | public static DiscuzTopListFragment getNewFragment(int fid) { 26 | DiscuzTopListFragment fragment = new DiscuzTopListFragment(); 27 | Bundle bundle = new Bundle(); 28 | bundle.putInt(ARG_LIST_LAYOUT, R.layout.fragment_simple_list); 29 | bundle.putInt(ARG_ITEM_LAYOUT, R.layout.fragment_thread_item); 30 | bundle.putInt("fid", fid); 31 | fragment.setArguments(bundle); 32 | return fragment; 33 | } 34 | 35 | @Override 36 | @SuppressWarnings("unchecked") 37 | public void onLoadingMore(CommonListFragment fragment, final List listData) { 38 | Discuz.execute("toplist", new HashMap() {{ 39 | put("fid", getArguments().getInt("fid")); 40 | }}, null, new Response.Listener() { 41 | @Override 42 | public void onResponse(JSONObject data) { 43 | int total = -1; 44 | listData.clear(); 45 | if (data.has(Discuz.VOLLEY_ERROR)) { 46 | Helper.toast(R.string.network_error_toast); 47 | } else if (!data.isNull("Variables")) { 48 | try { 49 | JSONObject var = data.getJSONObject("Variables"); 50 | JSONArray threads = var.getJSONArray("forum_threadlist"); 51 | for (int i = 0; i < threads.length(); i++) { 52 | listData.add(new Thread(threads.getJSONObject(i))); 53 | } 54 | total = listData.size(); 55 | } catch (JSONException e) { 56 | Log.d("Discuz", "Load Top List Failed (" + e.getMessage() + ")"); 57 | } 58 | } 59 | loadMoreDone(total); 60 | } 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/fragment/SimpleLayoutFragment.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.nyasama.R; 10 | 11 | /** 12 | * Created by oxyflour on 2014/11/18. 13 | * 14 | */ 15 | public class SimpleLayoutFragment extends Fragment { 16 | private int mLayoutId; 17 | 18 | @Override 19 | public void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | Bundle bundle = getArguments(); 22 | if (bundle != null) 23 | mLayoutId = bundle.getInt("layout_id"); 24 | if (mLayoutId <= 0) 25 | mLayoutId = R.layout.fragment_blank; 26 | } 27 | 28 | @Override 29 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 30 | return inflater.inflate(mLayoutId, container, false); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/util/BitmapLruCache.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.util; 2 | 3 | import android.graphics.Bitmap; 4 | import android.util.LruCache; 5 | 6 | import com.android.volley.toolbox.ImageLoader; 7 | 8 | /** 9 | * Created by oxyflour on 2014/11/19. 10 | * REF: http://blog.lemberg.co.uk/volley-part-3-image-loader 11 | * 12 | * 13 | * 用来为ThisApp 中的 imageLoader 提供存储空间的 Cache 14 | * 其实用起来和 Hashmap 没什么两样 15 | * 16 | * getBitmap 和 putBitmap 是 ImageCache 中的方法 17 | * getBitmap 是按URL 取出bitmap 18 | * putBitmap 是当下载完成后,放入Cache 19 | * 20 | * 其他是LruCache中的方法,LruCache 的意思是 使用 Last Recent Used 算法的 Cache 21 | * 至于具体为什么默认要用 maxmemory/8 Kbytes 左右的空间,我也不明白 22 | * 23 | * 24 | */ 25 | public class BitmapLruCache 26 | extends LruCache 27 | implements ImageLoader.ImageCache { 28 | 29 | public BitmapLruCache() { 30 | this(getDefaultLruCacheSize()); 31 | } 32 | 33 | public BitmapLruCache(int sizeInKiloBytes) { 34 | super(sizeInKiloBytes); 35 | } 36 | 37 | @Override 38 | protected int sizeOf(String key, Bitmap value) { 39 | return value.getRowBytes() * value.getHeight() / 1024; 40 | } 41 | 42 | @Override 43 | public Bitmap getBitmap(String url) { 44 | return get(url); 45 | } 46 | 47 | @Override 48 | public void putBitmap(String url, Bitmap bitmap) { 49 | put(url, bitmap); 50 | } 51 | 52 | public static int getDefaultLruCacheSize() { 53 | final int maxMemory = 54 | (int) (Runtime.getRuntime().maxMemory() / 1024); 55 | 56 | return maxMemory / 8; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/util/CallbackMatcher.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.util; 2 | 3 | import java.util.regex.MatchResult; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | /** 8 | * Created by oxyflour on 2014/11/22. 9 | * REF: http://stackoverflow.com/questions/375420/java-equivalent-to-phps-preg-replace-callback 10 | */ 11 | public class CallbackMatcher { 12 | public static interface Callback 13 | { 14 | public String foundMatch(MatchResult matchResult); 15 | } 16 | 17 | private final Pattern pattern; 18 | 19 | public CallbackMatcher(String regex, int style) 20 | { 21 | this.pattern = Pattern.compile(regex, style); 22 | } 23 | 24 | public String replaceMatches(String string, Callback callback) 25 | { 26 | final Matcher matcher = this.pattern.matcher(string); 27 | while(matcher.find()) 28 | { 29 | final MatchResult matchResult = matcher.toMatchResult(); 30 | final String replacement = callback.foundMatch(matchResult); 31 | string = string.substring(0, matchResult.start()) + 32 | replacement + string.substring(matchResult.end()); 33 | matcher.reset(string); 34 | } 35 | return string; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/util/CommonListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.util; 2 | 3 | import android.util.SparseArray; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.BaseAdapter; 8 | import android.widget.TextView; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created by Oxyflour on 2014/11/13. 14 | * REF: http://blog.csdn.net/lmj623565791/article/details/38902805 15 | * 16 | * commonListAdapter 是为各种ListView 准备的适配器,也是一个通用工具 17 | * 因此在这里更多的考虑的是通用性而非具体实现,用例应该有不少,比如DiscuzForumIndexFragment 中的用例 18 | * 也可以把这个类做成接口啦 19 | */ 20 | public abstract class CommonListAdapter extends BaseAdapter { 21 | 22 | protected List mList; 23 | protected int mLayout; 24 | //构造函数区 25 | public CommonListAdapter() { 26 | } 27 | 28 | public CommonListAdapter(List list, int layout) { 29 | setup(list, layout); 30 | } 31 | 32 | public void setup(List list, int layout) { 33 | mList = list; 34 | mLayout = layout; 35 | } 36 | //构造函数区 37 | 38 | //基本(酱油)函数区 39 | @Override 40 | public int getCount() { 41 | return mList.size(); 42 | } 43 | 44 | @Override 45 | public T getItem(int position) { 46 | return mList.get(position); 47 | } 48 | 49 | @Override 50 | public long getItemId(int position) { 51 | return position; 52 | } 53 | //基本(酱油)函数区 54 | 55 | //重点函数区 56 | /* 57 | Adapter 的文档:http://developer.android.com/reference/android/widget/Adapter.html 58 | adapter 的工作原理是: 59 | 自动创建用户能看见的View以及接下来可能会看见的View 60 | 当adapter 创建View 时,就会自动调用getView 函数 61 | 比较值得说的时第二个参数,convertView 是被用户移到视窗外的View 62 | 因此可以拿来,装上新的数据重用 63 | */ 64 | @Override 65 | @SuppressWarnings("unchecked") 66 | public View getView(int position, View convertView, ViewGroup parent) { 67 | if (convertView == null) { 68 | convertView = createView(parent, position); 69 | convertView.setTag(new ViewHolder(convertView)); 70 | } 71 | convertView((ViewHolder) convertView.getTag(), getItem(position)); 72 | return convertView; 73 | } 74 | //这个有用过,应该用不着加 @SuppressWarnings("unused") 了 75 | @SuppressWarnings("unused") 76 | public View createView(ViewGroup parent, int position) { 77 | return LayoutInflater.from(parent.getContext()) 78 | .inflate(mLayout, parent, false); 79 | } 80 | 81 | //重写这个函数,能改变convertView中的内容 82 | public abstract void convertView(ViewHolder viewHolder, T item); 83 | 84 | //这个类一方面管理convertView中的组件,另一方面也是为了提高效率 85 | public class ViewHolder { 86 | //SparseArray 是比较推荐存储方式 87 | //文档见:https://developer.android.com/training/articles/memory.html 中的 Use optimized data containers 节 88 | private final SparseArray mViews; 89 | private final View mConvertView; 90 | 91 | public ViewHolder(View convertView) { 92 | mViews = new SparseArray(); 93 | mConvertView = convertView; 94 | } 95 | 96 | public View getConvertView() { 97 | return mConvertView; 98 | } 99 | //findViewById 是比较浪费时间的操作,因此创建的时候放入Array,以后从Array 中找比较快 100 | public View getView(int viewId) { 101 | View view = mViews.get(viewId); 102 | if (view == null) { 103 | view = mConvertView.findViewById(viewId); 104 | mViews.put(viewId, view); 105 | } 106 | return view; 107 | } 108 | 109 | public void setText(int viewId, CharSequence text) { 110 | TextView view = (TextView) getView(viewId); 111 | if (view != null) 112 | view.setText(text); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/util/HtmlImageGetter.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.util; 2 | 3 | import android.content.res.Resources; 4 | import android.graphics.Bitmap; 5 | import android.graphics.Point; 6 | import android.graphics.drawable.BitmapDrawable; 7 | import android.graphics.drawable.Drawable; 8 | import android.graphics.drawable.LevelListDrawable; 9 | import android.text.Html; 10 | import android.widget.TextView; 11 | 12 | import com.android.volley.Response; 13 | import com.android.volley.toolbox.ImageLoader; 14 | import com.android.volley.toolbox.ImageRequest; 15 | import com.nyasama.ThisApp; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | /** 21 | * Created by oxyflour on 2014/11/19. 22 | * 23 | */ 24 | public class HtmlImageGetter implements Html.ImageGetter { 25 | 26 | public static class HtmlImageCache { 27 | public ImageLoader.ImageCache images; 28 | public Map size; 29 | public HtmlImageCache(ImageLoader.ImageCache cache) { 30 | this.images = cache; 31 | this.size = new HashMap(); 32 | } 33 | } 34 | 35 | private TextView container; 36 | private HtmlImageCache cache; 37 | private Point maxSize; 38 | private int jobs; 39 | 40 | public HtmlImageGetter(TextView container, HtmlImageCache cache, Point maxSize) { 41 | this.container = container; 42 | this.cache = cache; 43 | this.maxSize = maxSize; 44 | } 45 | 46 | @Override 47 | public Drawable getDrawable(String s) { 48 | if (s == null) return null; 49 | 50 | final String url = Discuz.getSafeUrl(s); 51 | final LevelListDrawable drawable = new LevelListDrawable(); 52 | 53 | // for smilies, we will cache it in Discuz 54 | boolean isSmileyUrl = Discuz.Smiley.isSmileyUrl(url); 55 | final ImageLoader.ImageCache imageCache = isSmileyUrl ? Discuz.Smiley.getCache() : cache.images; 56 | final Map imageSize = cache.size; 57 | final int imageWidth = isSmileyUrl ? 0 : maxSize.x; 58 | final int imageHeight = isSmileyUrl ? 0 : maxSize.y; 59 | 60 | final Resources resources = ThisApp.context.getResources(); 61 | Bitmap cachedImage = imageCache != null ? 62 | imageCache.getBitmap(url) : 63 | null; 64 | Drawable empty = cachedImage != null ? 65 | new BitmapDrawable(resources, cachedImage) : 66 | resources.getDrawable(android.R.drawable.ic_menu_gallery); 67 | Point size = imageSize.containsKey(url) ? imageSize.get(url) : null; 68 | drawable.addLevel(0, 0, empty); 69 | drawable.setBounds(0, 0, 70 | size != null ? size.x : empty.getIntrinsicWidth(), 71 | size != null ? size.y : empty.getIntrinsicHeight()); 72 | 73 | if (cachedImage == null && imageWidth >= 0 && imageHeight >= 0) { 74 | jobs ++; 75 | ImageRequest request = new ImageRequest(url, new Response.Listener() { 76 | @Override 77 | public void onResponse(final Bitmap bitmap) { 78 | drawable.addLevel(1, 1, new BitmapDrawable(resources, bitmap)); 79 | drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight()); 80 | drawable.setLevel(1); 81 | // save to cache 82 | if (imageCache != null) 83 | imageCache.putBitmap(url, bitmap); 84 | if (imageSize != null) 85 | imageSize.put(url, new Point(bitmap.getWidth(), bitmap.getHeight())); 86 | // refresh layout 87 | jobs --; 88 | if (jobs == 0) { 89 | // TODO: find a better way to refresh the layout 90 | container.setText(container.getText()); 91 | } 92 | } 93 | }, imageWidth, imageHeight, null, null); 94 | ThisApp.requestQueue.add(request); 95 | } 96 | 97 | return drawable; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/util/MultipartRequest.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.util; 2 | 3 | import com.android.volley.AuthFailureError; 4 | import com.android.volley.NetworkResponse; 5 | import com.android.volley.Request; 6 | import com.android.volley.Response; 7 | import com.android.volley.VolleyLog; 8 | 9 | import org.apache.http.entity.mime.HttpMultipartMode; 10 | import org.apache.http.entity.mime.MultipartEntity; 11 | import org.apache.http.entity.mime.content.ContentBody; 12 | 13 | import java.io.ByteArrayOutputStream; 14 | import java.io.IOException; 15 | import java.util.Map; 16 | import java.util.UUID; 17 | 18 | /** 19 | * Created by oxyflour on 2014/11/20. 20 | * REF: http://stackoverflow.com/questions/16797468/how-to-send-a-multipart-form-data-post-in-android-with-volley 21 | */ 22 | public class MultipartRequest extends Request { 23 | 24 | private Response.Listener mListener; 25 | private MultipartEntity entity; 26 | 27 | public MultipartRequest(String url, Map body, 28 | Response.Listener onSuccess, Response.ErrorListener onError) { 29 | super(Method.POST, url, onError); 30 | mListener = onSuccess; 31 | entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, 32 | "****************" + UUID.randomUUID().toString().replace("-", "").substring(0, 15), null); 33 | for (Map.Entry entry : body.entrySet()) 34 | entity.addPart(entry.getKey(), entry.getValue()); 35 | 36 | } 37 | 38 | @Override 39 | public String getBodyContentType() { 40 | return entity.getContentType().getValue(); 41 | } 42 | 43 | @Override 44 | public byte[] getBody() throws AuthFailureError { 45 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 46 | try { 47 | entity.writeTo(stream); 48 | } 49 | catch (IOException e) { 50 | VolleyLog.e("IOException writing to ByteArrayOutputStream (" + e.getMessage() + ")"); 51 | } 52 | return stream.toByteArray(); 53 | } 54 | 55 | @Override 56 | protected Response parseNetworkResponse(NetworkResponse networkResponse) { 57 | return Response.success(new String(networkResponse.data), getCacheEntry()); 58 | } 59 | 60 | @Override 61 | @SuppressWarnings("unchecked") 62 | protected void deliverResponse(String s) { 63 | mListener.onResponse(s); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/util/PersistenceCookieStore.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.util; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import java.net.HttpCookie; 7 | import java.net.URI; 8 | import java.util.HashSet; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | /** 13 | * Created by oxyflour on 2014/11/15. 14 | * 15 | * 把cookiestore中的数据存入sharedpreference 16 | * 把sharedpreference中的数据取出放入cookiestore 17 | * 18 | * 在Discuz中见到其调用,具体目的,用法还不明确 19 | * InMemoryCookieStore 文档:http://www.docjar.com/docs/api/sun/net/www/protocol/http/InMemoryCookieStore.html 20 | */ 21 | public class PersistenceCookieStore extends InMemoryCookieStore { 22 | 23 | private SharedPreferences mPrefs; 24 | 25 | @SuppressWarnings("unchecked") 26 | public PersistenceCookieStore(Context context) { 27 | super(); 28 | mPrefs = context.getSharedPreferences("PersistenceCookiePref", Context.MODE_PRIVATE); 29 | Map saved = mPrefs.getAll(); 30 | for (Map.Entry entry : saved.entrySet()) { 31 | URI uri = URI.create(entry.getKey()); 32 | for (String cookieStr : (Set) entry.getValue()) 33 | for (HttpCookie cookie : HttpCookie.parse(cookieStr)) 34 | add(uri, cookie); 35 | } 36 | } 37 | 38 | public void save() { 39 | SharedPreferences.Editor editor = mPrefs.edit(); 40 | editor.clear(); 41 | for (URI uri : getURIs()) { 42 | Set cookieStrs = new HashSet(); 43 | for (HttpCookie ck : get(uri)) 44 | cookieStrs.add(ck.toString()); 45 | editor.putStringSet(uri.toString(), cookieStrs); 46 | } 47 | editor.apply(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/util/PhotoViewPager.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.util; 2 | 3 | import android.content.Context; 4 | import android.support.v4.view.ViewPager; 5 | import android.util.AttributeSet; 6 | import android.view.MotionEvent; 7 | 8 | /** 9 | * Created by oxyflour on 2014/12/7. 10 | * AUTHOR: azi 11 | * REF: https://github.com/chrisbanes/PhotoView/issues/31 12 | */ 13 | public class PhotoViewPager extends ViewPager { 14 | 15 | public PhotoViewPager(Context context) { 16 | super(context); 17 | } 18 | 19 | public PhotoViewPager(Context context, AttributeSet attrs) { 20 | super(context, attrs); 21 | } 22 | 23 | @Override 24 | public boolean onTouchEvent(MotionEvent ev) { 25 | try { 26 | return super.onTouchEvent(ev); 27 | } catch (IllegalArgumentException ex) { 28 | ex.printStackTrace(); 29 | } 30 | return false; 31 | } 32 | 33 | @Override 34 | public boolean onInterceptTouchEvent(MotionEvent ev) { 35 | try { 36 | return super.onInterceptTouchEvent(ev); 37 | } catch (IllegalArgumentException ex) { 38 | ex.printStackTrace(); 39 | } 40 | return false; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/util/SafeHtmlText.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.util; 2 | 3 | import android.content.Context; 4 | import android.text.Selection; 5 | import android.text.Spannable; 6 | import android.util.AttributeSet; 7 | import android.widget.TextView; 8 | 9 | /** 10 | * Created by oxyflour on 2015/6/15. 11 | * ref: https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=137509 12 | */ 13 | public class SafeHtmlText extends TextView { 14 | public SafeHtmlText(Context context) { 15 | super(context); 16 | } 17 | 18 | public SafeHtmlText(Context context, AttributeSet attrs) { 19 | super(context, attrs); 20 | } 21 | 22 | public SafeHtmlText(Context context, AttributeSet attrs, int defStyle) { 23 | super(context, attrs, defStyle); 24 | } 25 | 26 | @Override 27 | protected void onSelectionChanged(int selStart, int selEnd) { 28 | if (selStart == -1 || selEnd == -1) { 29 | // @hack : https://code.google.com/p/android/issues/detail?id=137509 30 | CharSequence text = getText(); 31 | if (text instanceof Spannable) { 32 | Selection.setSelection((Spannable) text, 0, 0); 33 | } 34 | } else { 35 | super.onSelectionChanged(selStart, selEnd); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/nyasama/util/SimpleIndicator.java: -------------------------------------------------------------------------------- 1 | package com.nyasama.util; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.widget.LinearLayout; 8 | 9 | /** 10 | * Created by oxyflour on 2015/5/29. 11 | * 12 | */ 13 | public class SimpleIndicator extends LinearLayout { 14 | public SimpleIndicator(Context context) { 15 | super(context); 16 | } 17 | public SimpleIndicator(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | } 20 | public void createIndicators(int total, int resId) { 21 | LayoutInflater inflater = (LayoutInflater) getContext() 22 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 23 | removeAllViews(); 24 | for (int i = 0, n = total; i < n; i ++) 25 | inflater.inflate(resId, this); 26 | } 27 | public void setActive(int position) { 28 | for (int i = 0, n = getChildCount(); i < n; i ++) { 29 | View view = getChildAt(i); 30 | if (view != null) 31 | view.getBackground().mutate().setAlpha(i == position ? 255 : 80); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-hdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-hdpi/ic_action_new.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-hdpi/ic_action_reply.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-hdpi/ic_action_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-hdpi/ic_action_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_send_now.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-hdpi/ic_action_send_now.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-hdpi/ic_drawer.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_drawer_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-hdpi/ic_drawer_dark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_menu_emoticons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-hdpi/ic_menu_emoticons.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-mdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-mdpi/ic_action_new.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-mdpi/ic_action_reply.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-mdpi/ic_action_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-mdpi/ic_action_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_send_now.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-mdpi/ic_action_send_now.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-mdpi/ic_drawer.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_drawer_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-mdpi/ic_drawer_dark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_menu_emoticons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-mdpi/ic_menu_emoticons.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xhdpi/ic_action_new.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xhdpi/ic_action_reply.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xhdpi/ic_action_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xhdpi/ic_action_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_send_now.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xhdpi/ic_action_send_now.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xhdpi/ic_drawer.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_drawer_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xhdpi/ic_drawer_dark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_menu_emoticons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xhdpi/ic_menu_emoticons.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_action_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xxhdpi/ic_action_new.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_action_reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xxhdpi/ic_action_reply.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_action_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xxhdpi/ic_action_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_action_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xxhdpi/ic_action_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_action_send_now.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xxhdpi/ic_action_send_now.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xxhdpi/ic_drawer.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_drawer_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xxhdpi/ic_drawer_dark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_menu_emoticons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable-xxhdpi/ic_menu_emoticons.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ab_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable/ab_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/default_board_icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable/default_board_icon.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/dot_indicator.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/joessr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable/joessr.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_anim1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable/splash_anim1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_anim2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NSDN/NyaSama/ab5b52d0b74c5a1a8c4b8c1b3901089f3373ec1f/app/src/main/res/drawable/splash_anim2.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_attachment_viewer.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 20 | 21 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_login.xml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 26 | 27 | 34 | 35 | 43 | 44 | 50 | 51 | 56 | 57 |