diff --git a/README.md b/README.md index 6bcd55c..a0506b9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,864 @@ -# home-school-contact-app +# 基于Android的家庭学校联系平台APP开发与实现 -基于Android的家庭学校联系平台APP开发与实现 \ No newline at end of file +# 摘要 + +学校教育与家庭教育的不一致,容易产生教育断层的局面,而现有的校讯通等家校互动平台,又存在教师与家长单向沟通等方面的问题。如今信息技术的飞速发展为家校共育的健康发展提供了强有力的保障,“互联网+”已成趋势。学校和教师要一改过去传统的沟通交流方式,借用互联网与手机短信相结合的家校互动信息平台,扩展沟通交流渠道,架起家校沟通的桥梁,从而让家校教育形成合力,提高教育的时效性,促进学生健康成长。因此,本文就目前“互联网+教育”的新趋势进行了说明,以明确进行研究的目的。随后,将对本平台实现的过程和原理进行一一探讨,以让读者能了解实现其的具体方法。最后,为了实现这个软件平台,本文对这个软件作了系统分析和系统设计,最终实现了该软件。通过测试,该平台能正常运行在Android系统下的智能设备,也验证了本文所探讨的设计正确性和重要性。 + +**关键词:**互联网、家校互动、家校协作、功能应用 + +# Abstract + +Nowadays, the inconsistency of school education and family education may easily result in education gap. And the existing home-school interactive platforms like School Paper still have some problems including the issue of one-way communication between teachers and parents. The rapid development of information technology has provided a strong guarantee for the healthy development of home-school coeducation. Currently, the” Internet+” mode has become a trend, which requires the schools and teachers update the traditional communication ways with parents. They can combine the internet with mobile phone text message to establish a home-school interaction information platform, expanding the communication channel, setting up the bridge of communication so as to produce a unified strength of home-school education, improving the efficiency of education and promote the healthy growth of students. Therefore, This paper puts forward the aim of this research by describing the current trend of “Internet +Education”. In addition, It explores the process and principles of the new platform to make the readers understand the specific methods. Finally, in order to achieve this software platform, this paper makes a systematic analysis and system design. Through the test, the platform can run in the intelligent equipment of Android system, thus verifying the correctness and importance of the design discussed in this paper. + +**Key words:** Internet; home schoolinteraction; home school cooperation; Function Application + +# 1. 概述 + +## 1.1 研究背景和意义 + +社会的发展,智能手机的普及让各种各样的手机应用APP成为我们生活中必不可少的一部分,教育行业也搭上了这趟车,走上了“互联网+”教育,为了方便学生、家长、学校三体互动,让家长和学生能及时收到学校发送的消息,满足用户之前的实时交流,“爱吖校推”应运而生。 + +“爱吖校推”是一款基于Android的家校互动平台。随着社会的发展,各种APP在手机行业发展的助推下应用越来越广泛。某权威调研机构表示,2016年,Android系统已经占领市场份额高达81.3%,而大名鼎鼎的iPhone屈居第二,仅占17.8%,更令人惊奇的是,Android的市场份额还在持续增多。 + +社交是人类社会性群体的基本属性。而“爱吖校推”就是一款基于教育行业的社交类APP。它支持所有的即时通讯应该包含的功能,文件发送、位置发送、音视频通话、图片、视频等,同样也支持校方和教师发送公告作业并推送到相应群体的Android智能终端。在当前微信用户如日中天的基础上,“爱吖校推”采用微信朋友圈的方式,支持消息发送、点赞、评论、拍照、秒拍、微视频等群体社交,真正进入微社交时代。这是一件非常有意义的事情。 + +## 1.2 国内现状 + +家校互动的需求长期已有,它的研究和设计从本世纪初就开始了,而且也取得了不小的成效。但在早期由于技术的限制,所以存在信息的单项沟通,比如早期的“校讯通”。它就是单纯的通过收发短信来达到家校信息交流,教师得不到任何的反馈。之后随着移动互联网的发展,微信平台如鱼得水,其双向沟通性让一线教师感觉是雪中送炭,但其信息筛选性一直为人诟病。虽然微信等即时通讯软件一定意义上解决了家校互动的问题,但这样的处理,无疑是增加了教师的工作量。 + +近年来,有 “爱上学”、“和校园”、“爱学习”、“校讯通”等已经运行的家校互动支持平台20多个,这些平台主要实现信息发布查看等功能,对于信息的及时推送功能匮乏,加之在校大学生多用QQ群或者微信群作为沟通平台,常常使得通知公告信息错过,导致了学生之间的信息不对等,而国内超高量的外出打工家长,想看到没有手机的孩子实在困难。所以“爱吖校推”在操作简单的基础上,优化了拍照和微视频,让老师可以把学生的学校情况实时地分享给大家。消息的离线推送也使得信息的准确到达率迅速提升,而附带音视频通话的及时通讯板块,也是拉近了学生、家长、教师之间的距离。 + +## 1.3 论文的思路和结构 + +该论文分十个部分进行逐一讲解: + +首先把概述作为了第一部分,该系统平台的研究背景和意义以及国内现状作为主要讲述内容。 + +第二部分是研究方案和架构概述,主要阐述了本课题预计花费时间的设计方案、软件开发设计方案以及设计目标,最后做了架构概述。 + +第三部分是需求分析,主要从用户需求、性能需求和功能需求三方面阐述了需求板块需要具备的东西。本部分还用了用例图、用例说明增加相关人员的理解。 + +第四部分是概要设计,主要从Android端和服务器端分别阐述了功能总体设计,然后画了数据库E-R图,最后是系统类图和界面设计。 + +第五部分是详细设计,主要从数据库设计、CS协议通信、时序图三个方面阐述。 + +第六部分是系统实现,主要介绍了本次系统实现所用到的开发工具,并展示了开发界面总览和核心功能代码的讲解。 + +第七部分是软件测试,分版块进行功能、性能、安全、交叉事件以及兼容性板块进行测试并修改系统bug。 + +第八、九部分是本次毕业设计中的收获和结论以及自己的感想。 + +最后是本文所参考的各种有价值的资料列表。 + +# 2. 研究方案和架构概述 + +## 2.1 预计花费时间设计方案 + +- 花费7天查阅了关于即时通讯的资料以及小米推送的官方文档,并对其进行分析整理 + +- 花费10天查阅了一些技术博客和相关论坛以及GitHub上比较火的框架和项目 + +- 花费15天进行数据库设计,并对系统框架做一个全局性思考 + +- 花费10天编写后台API数据接口,并做简单测试 + +- 花费1个月编写Android端代码,并对后台数据可行性进行验证修改 + +- 最后进行常规测试,并在各大机器上运行,以保证不会出现致命Bug + +## 2.2 软件开发设计方案 + +采用MVC开发模式,按照功能可划分为:发通知,发作业,互评点赞,图片并茂,即时通讯,小米推送等模块。 + +功能模块细化: + +- **班级圈**:班级圈包含教师可发放通知、作业,基本支持图文并茂社区化和微视频上传。家长可查看自己班级的每一条信息,以及进行互评回复点赞 + +- **即时通讯**:即时通讯板块主要依赖于环信,在环信SDK的大前提下,引入基本的即时通讯和音视频通话 + +- **社区板块**:社区板块是用户只要在一个班级即可进行类似朋友圈的交流,依然可以进行互评点赞回复 + +- **发现板块**:发现板块主要是为加载一些广告和活动 + +- **我的板块**:我的板块主要是提供用户信息的更改设置等 + +- **推送板块**:当前推送继承了Google推送、华为推送和小米推送,以最大的可能提高推送接收率 + +## 2.3 本课题的设计目标 + +模块功能实现的目标: + +- **班级圈**:保证班级圈数据的正常显示,非本班人员应该不具备查看该班信息权限的能力。采用广播、接口回调及其其它方式完成数据的传递和更新 + +- **即时通讯**:保证音视频通话的离线呼起,保证长连接的引用,保证用户能正常收发消息 + +- **社区**:同班级圈 + +- **发现**:保证广告的通畅性和可行性 + +- **我的板块**:保证用户信息的修改处理正常,做到信息不泄漏 + +- **推送**:保证推送成功率与正确率 + +- **交互性良好**:采用materialdesign 风格设计,以及动画效果的引用,使用户和软件具有更加青睐的交互体验,并通过信息圈子推送,增加了用户粘性 + +- **代码风格佳**:在编码过程中,严格要求分包逻辑,采用模块化分包,并对代码进行合理的封装处理,使代码更加模块化,让其他人能更易上手 + +- **实用性**:通过不断的更新产品功能和接收用户反馈,让该产品更加地符合消费者思维 + +## 2.4 架构概述 + +本系统采用C/S架构,分为客户端和服务器端。 + +客户端被分为了表现层、业务逻辑层和数据访问层三个层面。 + +- **表现层**:主要通过Android应用页面来展示数据,以及一系列事件响应的UI控件 + +- **业务逻辑层**:主要用于业务逻辑的处理。通常由业务服务Service类和业务实体类Entity组成 + +- **数据访问层**:本系统采用的数据库是MySQL,采用XAMPP进行服务器搭建,采用PHP作为后台数据接口编写,用花生壳做域名解析,以达到Android客户端与服务器之间的访问 + +# 3. 需求分析 + +需求分析是“爱吖校推”应用分析的必要阶段,下面分软硬件需求、功能需求和用户需求三方面做介绍, + +## 3.1 软硬件需求 + +本系统的软硬件需求如下: + +- **在Android平台上运行**,系统在4.0以上 + +- **后台数据库**:MySQL + +- **开发环境**:JavaJDK1.7 ,Windows 10 + +- **开发工具**:AndroidStudio、Eclipse For PHP、XAMPP、 + +- **个人计算机**:华硕飞行堡垒笔记本 + +## 3.2 功能需求 + +“爱吖校推”作为一款功能性软件,其功能需求相当重要。以下为“爱吖校推”的功能需求: + +**发通知、发作业** + +发通知和发作业,是学校教师特有的功能,教师可以通过“爱吖校推”平台进行通知和作业的发放,每当发一条信息,该班的所有人员便可以收到来自服务器的信息推送,提醒家长打开APP查看。每一条通知和作业家长都可以点赞和互评和回复。这样让家长和学校的关系更贴切,也增加了信息筛选机制,从而避免了QQ群、微信群等多余信息的影响。 + +**传视频、传照片** + +传视频是在社区和通知作业板块均具备的功能,紧跟微视频的时代步伐,教师可以把孩子在学校的精彩表演,录制下来发到班群里,家长便可以看到。家长也可以把孩子在家里做的有意义的事情放到社区,让同一个班级的家长朋友们借鉴。良好的图文并茂社区化,不仅增进了家长和学校的交流,还增进了家长之间的联系和友谊。 + +**即时通讯** + +即时通讯板块是一个整体的板块,基本包含QQ微信的所有功能,依然可以发图片、发消息、发语音、发定位、音视频通话等。意在增加朋友之间的联系和家长和学校教师的一对一交流和多对多交流。 + +**发现板块** + +发现板块主要是轮播的一些优秀且有利于教师家长的APP功能板块,并且会组织一些活动,邀请大家参加。 + +**离线推送** + +离线推送在家校互动平台软件中是一个必备功能,也算是一个核心功能,有它才能保证用户真正收到来自教师发放的信息,以及即时通讯过来的消息。同时音视频通话等即时性要求较高的功能,也得依赖它。并且,推送信息到通知栏的方式向用户传递信息,也是可以增加用户粘性。 + +## 3.3 用户需求 + +在“爱吖校推”应用的开发过程中,为了尽量满足学校老师和家长用户的要求。目前得到的需求有: + +- 图片显示清晰,但不能太大,以免浪费流量 + +- 微视频的压缩要处理好,不能太浪费流量 + +- 即时通讯要通畅 + +- 要具备离线推送,确保家长用户能收到教师发送的作业和通知 + +- 要有权限管理,不能让外班人员看到本班的消息 + +- 公告和作业不能插广告 + +- 应用不能经常闪退 + +- 应用不能太大,也不能太占内存 + +- 运行要流畅,不能出现卡顿现象 + +## 3.4 用例图 + +### 3.4.1 登录板块 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/6138909c0da0575049377ef43e3b0279.writebug) + +### 3.4.2 班圈板块 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/fdcd5ec8f15f42161b6b9f75e0fc0ea0.writebug) + +### 3.4.3 消息板块 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/11d972e6972c3854dfe64962e989f62f.writebug) + +### 3.4.4 发现板块 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/d59d37f60fa3a4e21abf7c90ae73c730.writebug) + +### 3.4.5 我的板块 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/240cb69f902d9d7575f84f40b26225e3.writebug) + +## 3.5 用例说明 + +### 3.5.1 UC1用户登录 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/65e5c68cf43a3556df1276f47d4a04ac.writebug) + +### 3.5.2 UC2用户注册 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/f7c278e20635684af503e65203109d58.writebug) + +### 3.5.3 UC3找回密码 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/c6ee4a4cc9766a2fa69b7536d03b8802.writebug) + +### 3.5.4 UC4发布信息 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/897866eafcd4cc3bdfe3d3dd07dfe851.writebug) + +### 3.5.5 UC5查看所有信息 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/8392bca0a6240a17dc9affa204501a6f.writebug) + +### 3.5.6 UC6查看信息详情 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/f05e50e19c9425bd76bd733e28f38f3c.writebug) + +### 3.5.7 UC7点赞评论回复 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/bb09530905a87514525ab1fad79e7a97.writebug) + +### 3.5.8 UC8查看联系人和会话 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/b43a4e687791ec5e717743740649dfd5.writebug) + +### 3.5.9 UC9聊天 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/2665c813c2037221d40aba56ae9a65cc.writebug) + +### 3.5.10 UC10音视频通话 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/f6c2e69479a250aaacb59fdcf7a6f68f.writebug) + +### 3.5.11 UC11修改个人信息 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/0cf5d4114e98a999b3112663cdee3533.writebug) + +### 3.5.12 UC12修改孩子信息 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/3d746f9584ab9d7b539e2e208e191381.writebug) + +### 3.5.13 UC13退出登录 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/ad96395e56cd7c327446c6643b2a7680.writebug) + +# 4. 概要设计 + +## 4.1 系统功能总体设计图 + +### 4.1.1Android端功能总体设计图 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/aae74aa8d2ddfa0640c734b9f5183abf.writebug) + +### 4.1.2 服务器端功能总体设计图 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/a71ad85743e731250e6e0225b653f7e5.writebug) + +## 4.2 数据库E-R图设计 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/a9bab1ecbabc432385cab7313f99dcac.writebug) + +## 4.3 系统类图 + +### 4.3.1 APP端登录板块 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/c5a13c145509eab0503ba3c9b2a52a47.writebug) + +### 4.3.2 APP端主页板块 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/d40060ae6e4f270a20be48f6e3f459ec.writebug) + +### 4.3.3 APP端班圈板块 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/2e07225345252b0c4169e4b474234feb.writebug) + +### 4.3.4 APP端发布板块 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/1786fb557a0c2cbc77186c6a4986a29e.writebug) + +### 4.3.5 APP端消息板块 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/6c68f70b142b35443b47fbb91c606734.writebug) + +### 4.3.6 APP端发现板块 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/e53e756f71f60cb167eee2b8ea01235c.writebug) + +### 4.3.7 APP端我的板块 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/e298be3bf6e8c9d3fedd56a694afd8a0.writebug) + +## 4.4 界面设计 + +**图片选取界面设计** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/949e68f2066dbb66446cfd81d2688b3f.writebug) + +**图片选取界面设计** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/cd163a94614be73c4482949b7b1328b3.writebug) + +**登录界面设计** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/72793c3d5f02f6bb0f2a64d0fb421b1c.writebug) + +**手机号验证界面设计** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/5ae302c62197b1f3b08a5ce500963b4e.writebug) + +**主界面设计** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/0cc6fba67ab2c9ad21b38e3ebd001787.writebug) + +**课程表界面设计** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/23f1d6e55cf03004549868eebbc3ea21.writebug) + +**联系人列表界面设计** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/2959bc98336bc3c52df472f01694597f.writebug) + +**聊天界面设计** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/35b1838814b50246a6417d23314e4980.writebug) + +**音频呼叫界面设计** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/eeebd14c1718cece6b2cffed2eebf37b.writebug) + +**发布信息界面设计** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/9b1d9b6e7be0482f05b25f5d6a082272.writebug) + +**发布界面设计** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/be4b85cd5a26591ddb89d2f1ad2270b4.writebug) + +**我的板块界面设计** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/da80445ae480e15eeed48c54ee77cefb.writebug) + +**按住拍界面设计** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/cb18b74b1d9a5eb3987a39d7be00b3ea.writebug) + +# 5. 详细设计 + +## 5.1 数据库详细设计文档 + +本软件的数据库为MySQL数据库,主要是搭建在XAMPP上结合PHP存在。主要分为以下几个数据表: + +### 5.1.1 用户表设计(aiya_user) + +| 字段 | 属性 | 备注 | +| ------------ | ------------------ | --------------------- | +| username | varchar(20)(主键) | 账号(手机号) | +| password | varchar(20) | 密码 | +| nickname | varchar(20) | 用户昵称 | +| type | int(1) | 用户类型 (1教师、2 家长、3管理员) | +| classid | int(10)(外键) | 班级id | +| avatar | Varchar(100) | 用户头像地址 | +| birthday | date | 生日,实际存储为时间戳 | +| address | text(null) | 用户地址 | +| child_name | varchar(20) (null) | 孩子姓名 | +| child_avatar | text(null) | 孩子头像地址 | + +### 5.1.2 班级信息表设计(aiya_class) + +| 字段 | 属性 | 备注 | +| ---------- | ------------- | ---- | +| classid | int(10)(主键自增) | 班级id | +| classname | varchar(50) | 班级名称 | +| schoolname | text | 学校名称 | + +### 5.1.3 主贴表设计(aiya_main) + +| 字段 | 属性 | 备注 | +| -------- | --------------- | -------------------------- | +| mainid | int(10)(主键自增) | 帖子id, | +| classid | int(10)(外键) | 班级id | +| username | varchar(20)(外键) | 用户名 | +| time | timestamp | 发布时间,实际存储相当于long型时间戳 | +| infotype | int(1) | 主贴类型(1 代表公告 2 代表作业 3 代表动态) | +| content | text | 帖子内容 | + +### 5.1.4 评论表设计(aiya_comment) + +| 字段 | 属性 | 备注 | +| -------- | --------------- | ----------------------- | +| infoid | int(11)(主键自增) | 信息id | +| mainid | int(10)(外键) | 主贴id,用于识别隶属于哪一条帖子的评论 | +| username | varchar(20)(外键) | 用户名,用于识别发布人信息 | +| time | bigint(20) | 发布时间,long型时间戳 | +| content | text | 发布内容 | +| reply | varchar(20)(外键) | 用户表username作为外键,用于回复@功能 | + +### 5.1.5 点赞表设计(aiya_praise) + +| 字段 | 属性 | 备注 | +| -------- | ------------- | ------------------ | +| praiseid | int(10)(主键自增) | 点赞信息id | +| mainid | int(10)(外键) | 主贴表外键,用于识别赞的是哪一条主贴 | +| username | varchar(20) | 用户表外键,用于识别是谁点赞了 | + +### 5.1.6 主贴图片表设计(aiya_pic) + +| 字段 | 属性 | 备注 | +| ------ | -------------- | ------ | +| picid | int(10)(主键自增) | 图片id | +| mainid | int(10)(主贴表外键) | 主贴id | +| url | text | 图片存放地址 | + +## 5.2 CS协议通信文档 + +说明:返回格式为code,msg,data三个字段,code为0是代表请求逻辑正确,-1为请求异常; + +### 5.2.1 用户系统 + +**获取用户是否注册APP** + +接口地址: /user/usable_mobile.PHP + +方式和返回:GET JSON + +请求参数: + +| 名称 | 类型 | 说明 | +| ------ | ------ | ----- | +| mobile | string | 用户手机号 | + +**注册** + +接口地址: /user/register.PHP + +方式和返回:POST JSON + +请求参数: + +| 名称 | 类型 | 说明 | +| -------- | ------ | ------------- | +| username | string | 用户手机号 | +| password | string | 用户密码 | +| nickname | string | 昵称 | +| birthday | string | 生日,传递long型时间戳 | +| avatar | string | 头像上传的地址 | + +**登录** + +接口地址: /user/login.PHP + +方式和返回:POST JSON + +请求参数: + +| 名称 | 类型 | 说明 | +| -------- | ------ | ----- | +| username | String | 用户手机号 | +| password | string | 用户密码 | + +**重置密码** + +接口地址: /user/reset_pwd.PHP + +方式和返回:POST JSON + +请求参数: + +| 名称 | 类型 | 说明 | +| -------- | ------ | ----- | +| username | string | 用户手机号 | +| password | string | 用户新密码 | + +**上传头像** + +接口地址: /user/avatar.PHP + +方式和返回:POST JSON + +请求参数: + +| 名称 | 类型 | 说明 | +| ---- | ---- | ------- | +| file | File | 需要上传的文件 | + +**更新头像url** + +接口地址: /user/update_avatar.PHP + +方式和返回:GET JSON + +请求参数: + +| 名称 | 类型 | 说明 | +| -------- | ------ | ------------ | +| username | string | 用户名 | +| iconUrl | string | 头像地址 | +| type | int | 类型1为自己, 2为孩子 | + +### 5.2.2 信息系统 + +**异步获取主贴等信息** + +接口地址: /info/info_main.PHP + +方式和返回:GET JSON + +请求参数: + +| 名称 | 类型 | 说明 | +| -------- | ---- | ------------------------ | +| classid | int | 班级id,用于识别可见度 | +| infotype | int | 信息类型 1公告2作业3动态 | +| count | int | 信息起始数,默认一次获取10条,需要更改联系后台 | + +**获取发布信息人的信息** + +接口地址: /info/get_user.PHP + +方式和返回:GET JSON + +请求参数: + +| 名称 | 类型 | 说明 | +| -------- | ------ | ---- | +| username | string | 用户名 | + +**评论信息** + +接口地址: /info/insert_comment.PHP + +方式和返回:POST JSON + +请求参数: + +| 名称 | 类型 | 说明 | +| -------- | ------ | ------ | +| mainId | int | 主贴id | +| username | string | 用户名 | +| content | string | 评论内容 | +| reply | string | 回复人用户名 | + +**更新点赞信息** + +接口地址: /info/praise.PHP + +方式和返回:GET JSON + +请求参数: + +| 名 | 类型 | 说明 | +| -------- | ------ | ---- | +| mainId | int | 主贴id | +| username | string | 用户名 | + +## 5.3 时序图 + +### 5.3.1 登录时序图 + +该时序图是实现用例UC1用户的登录。 + +- 用户进入LoginActivity登录界面后按照提示输入账号名(必须为正确的手机号)和密码(不少于6位) + +- 先采用StringUtil工具类对输入数据进行验证,再把LoginPresenter把输入的数据传递给网络交互类AppService,让其与服务器进行数据交互并返回给LoginPresenter,通过回调机制让View层显示相关信息,若是登录成功则正确跳转到应用主页面,否则显示相关错误信息 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/79882ffef9d2cfe7238acfdf8da0a13b.writebug) + +### 5.3.2 发布时序图 + +该时序图是实现用例UC4发布信息。 + +- 用户进入发布页面,可以输入相关话题信息,也可上传附件(微视频和图片不共存) + +- 如果上传附件,则调用压缩相关的工具类进行附件压缩,如果压缩失败,则显示相关错误信息 + +- 未输入信息无法点击发布,如果点击发布按钮,则让ReleasPresenter处理相关逻辑,并把发布话题的信息传递给AppService类做网络访问处理,服务器返回相关信息,采用回调机制让View显示出相关信息 + +- 如果发布成功,则返回到主页面,并发送广播提示主页面进行数据刷新 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/e3075b7e28c2b31fa70d2ca5d13d5596.writebug) + +### 5.3.3 圈子信息时序图 + +该时序图是实现用例UC6查看信息详情。 + +- 用户在主页面可以看到话题相关信息(包括通知、作业、社区) + +- 如果点击任何一条信息,则可以跳转到详情页面,可以查看到相关点赞信息和评论信息 + +- 点击评论可以对该条话题信息进行评论,点击评论人可对该用户进行回复 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/f02379593c516587d95273f2ae0f0d65.writebug) + +### 5.3.4 聊天时序图 + +该时序图是实现用例UC9聊天。 + +- 用户可以从会话页面或者联系人页面进入聊天页面ChatActivity + +- 可以发送任何的文本消息,也可以点击下方“加号”按钮进行语音图片视频等文件的发送 + +- 可以直接调用音视频通话,向对方发起通话 + +- 任何的与服务器交互逻辑均交给EMClient类进行处理 + +- 被呼叫的用户可以选择拒绝音视频通话并把相关信息返回给EMClient类 + +- 监听类收到EMClient返回的信息后处理相应回调,显示相关信息 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/2656ab164af05af5807b50638069513e.writebug) + +# 6. 系统实现 + +## 6.1 开发工具简介 + +Android Studio :AndroidStudio 是Google推广的一款全新的Android开发工具,采用全新的Gradle方式进行编译,同时对原有的Eclipse开发的项目进行了支持。在2016年年底,Google宣布停止对Eclipse的支持与维护,彻底地宣布了Android Studio作为“Google亲儿子”的地位。其强大的市场占有率成为了使用趋势,我们不能墨守成规,需要向着新趋势看齐。 + +XAMPP: XAMPP(Apache+MySQL+PHP+PERL)原来的名字叫 LAMPP,但最新的几个版本被更名为XAMPP,主要是为了避免误解。它作为一款建站集成软件包,功能非常完善,其强大的兼容性更是征服了用户,不仅提供了Windows、Mac等主流操作系统,更是对Linux、Solaris等其它操作系统做了支持。更完美的是,它还支持包含简体中文、繁体中文、英文、韩文等多国语言包。但XAMPP最著名的还是它的便捷性,使用XAMPP只需要下载、解压、启动三个步骤就能让Apache服务器运行在机器上,并且还支持读取PHP文件以及集成了MySQL的使用。 EclipseFor PHP:这款软件是Eclipse分支下专用于开发PHP的一款IDE,支持PHP5和PHP7,在这里,我们主要用它来开发后台接口板块。 + +## 6.2 开发界面总览 + +### 6.2.1 Android开发界面总览 + +**Android 源码分包预览** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/3662af5a6197c3da9e309b0dbc7e80a9.writebug) + +**Android 资源文件预览** + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/affac6058735375b202bed943424b69d.writebug) + +### 6.2.2 PHP开发界面总览 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/98313a1f43173fa25a6d1f2e9294a05f.writebug) + +### 6.2.3 数据库操作页面总览 + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/fcf4e7b74a24006929d3bd432cafa203.writebug) + +## 6.3 核心功能代码 + +### 6.3.1 图片压缩处理 + +项目中的图片压缩来源于我GitHub已经开源的一个开源库,目前项目已经得到超700 Stars,主要采取BitmapFactory的内部类Options以及Bitmap下的createScaleBitmap方法对图片进行质量压缩和尺寸压缩。 + +思路: + +- Bitmap是一个相当大的对象,特别容易导致OOM,所以我们在压缩的时候并不能直接采用Bitmap,而采用BitmapFatory.Options。它有一个相当强大的属性:inJustDecodeBounds,当这个属性为true的时候,调用decode前缀的方法返回的就不是一个完整的Bitmap对象,而是null。因为它禁止这些方法为Bitmap分配内存,当设置这个属性为true时,便会复制Options的三个属性,它们分别是outWidth,outHeight和outMimeType。相当于不读取这个图片,却获取到了它的参数,的确很厉害。 + +- 另外一个不得不说的属性就是inSampleSize了,可以理解为压缩比率,设置好这个比率,就能调用decodeXXXX方法获得缩略图了,如果图片大小都一致,则可以定死它。可问题是我们的图片大小通常是不一致的,那我们压缩的重中之重就是获得这个正确的比率。因此,咱们完全能够经过我们想要的长宽,通过多次循环比对,从而达到等比例压缩。 + +- 然而, inSampleSize官方注释告诉我们一个必须注意的点:因为inSampleSize只能是2的整数次幂,意味着如何我们通过循环算出来inSampleSize为6的话,这时候只能向下取得整数次幂,也就是4。这样明显是达不到我们想要求的标准的 + +- Bitmap的createScaleBitmap这个方法成功消除了我们的焦虑,我们可以借用这个方法把我们之前得到的较大的缩略图进行二次缩小,直到完全符合我们的要求 + +核心代码为: + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/e6880457974d7176278ecc23bd0a8f3d.writebug) + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/ea5a949f51327acd2d339ab6c1e80b54.writebug) + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/b5a8ee83912e51a8bac0d19a65428101.writebug) + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/5704570d88301a1b86c2d15c8d4c3a91.writebug) + +### 6.3.2 相机适配处理 + +图片选取来自于我维护的一个开源库ImagePicker,目前GitHub Star数超过1300+,主要通过从数据库读取所有图片信息并返回到一个List中,该List将把所有图片的path存储在一起,然后把这些图片放在RecyclerView中显示,项目UI完全仿照微信做处理。为了解决Intent传值限制,我在项目中采用单例加锁的方式得以解决。 + +针对Android的适配上也是下了不少功夫,主要表现在Android 6.0 的动态权限处理,以及Android 7.0的相机打开限制,当然还有必不可少的MIUI系统坑和三星机器的图片旋转问题。 + +下面谈下解决方案: + +**6.0动态权限处理:** + +在Android 6.0 (API 23)开始,Android开始引入动态权限处理,即除了在之前的AndroidManifest.xml文件中申明权限,还需要在使用到权限的时候弹出用户是否授权的框。只需要重写onRequestPermissionsResult方法即可。示例代码如下: + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/04d4b3f2fb8d5274b6a26b9431232761.writebug) + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/1c0a29c4d182f6ef9a408763afff70d3.writebug) + +**对于调用系统相机拍照后图片旋转:** + +经常会遇到一种情况,拍照的时候看到照片是正的,但是当APP获取到这张图片的时候,却发现旋转了90度(也有可能是180,270,不过90度比较多见,这应该是手机传感器导致的)。为了解决这种不一定在所有机器上都出现的问题,我们可以引入Android系统提供的ExifInterface类来解决各个属性的操作。ExifInterface可以不用加载图片就获取到图片的长宽、旋转角度等多种属性,我们可以通过ExitInterface获取图片的旋转角度degree来进行处理,当满足degree不为0的时候,调用Matrix的postRotate进行角度旋转,核心代码为: + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/f863e8e7caaf8eb0f133b9a5502a2562.writebug) + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/a4f196e955629485da750c89158a98e7.writebug) + +**对于部分机型调起相机会回不去APP的适配处理(拍完照闪退问题):** + +这也是相机适配中必须处理的地方,由于Android系统厂商的ROM不一致,会让一些ROM对自带相机应用做优化,当某个APP通过Intent进入相机拍照界面时,系统会把这个APP最上层的Activity销毁回收,只需要重写onSaveInstanceState和onRestoreInstanceState方法对数据进行恢复和保存即可,核心代码为: + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/a280d2e57f8fc040e10b31c10fe5c4f9.writebug) + +**Android 7.0调用系统相机的处理:** + +由于Android 7.0 手机开始推广,所以我们也不得不处理7.0的权限问题。在Android 7.0 以后,file:// 不被允许作为一个附件的Uri的意图,否则会抛出FileUriExposedException,在这样的情况下,我们只需要用FileProvider即可解决。核心代码如下: + +```java + + + +``` + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/ed8147ee0b3c51bd74e80fcf5b91de82.writebug) + +![](http://www.write-bug.com/myres/static/uploads/2021/10/19/9b955d7b60b281ecdbaff4b9c95a0a23.writebug) + +# 7. 软件测试 + +基于Android等移动终端平台的APP软件测试与传统的软件测试不同,它不仅要求兼容性良好,而且要求响应时间要在一定的限制范围。比如用户的操作响应时间一般不能超过3-5秒,APP启动时间也不能太长。而对于Android操作系统,庞大的第三方厂商定制,导致Android系统各有差异。一个APP软件必须满足不用的屏幕分辨率都能正常显示,并且能够正确的完成相应功能。如果在某个环境下,界面功能显示不全,则会导致软件功能无法正确使用,也就失去了安装此软件的意义,所以对其兼容性的要求也是很重要的一个方面。 + +## 7.1 功能模块测试 + +功能模块的测试是最基本的测试。我通过找出APP的测试点,然后采用两款手机,小米3S(Android 5.0)和小米5S(Android 7.0)以及Windows抓包工具Fidder分别对“爱吖校推”的功能模块和网络接口进行完整测试,在测试过程出现了几个小问题。 + +- 图片选择页面出现选择异常,而后得以解决,因为导包错误,导致指向了另外一个文件 + +- 发布信息后没有刷新页面的Bug,后面采用广播提醒UI刷新得以解决 + +在解决完相关bug后,进行了新一轮的测试,下面是简单的测试情况: + +### 7.1.1 用户登录注册模块测试 + +该模块测试主要是验证用户的注册登录是否能正常使用,任何不正确逻辑都应该给出相应的提示。在注册时,手机号必须符合规范,密码不得少于6位,否则提示输入不规范。注册时需要输入两次密码,并且密码相同,验证码输入必须正确,否则提示相应错误。登录板块,第二次登录应该自动登录。 + +| 测试项目 | 测试方法 | 预期结果 | 结论 | +| ---- | ------------------------ | ------ | ------- | +| 用户注册 | 在注册界面输入用户名,密码,其他信息(符合要求) | 注册成功 | 与预期结果一致 | +| 用户注册 | 在注册界面不输入内容或者输入信息不符合要求 | 注册失败 | 与预期结果一致 | +| 用户登录 | 在登录界面输入用户名,密码,且用户名和密码匹配 | 登录成功 | 与预期结果一致 | +| 用户登录 | 在登录界面不输入内容或者输入信息不正确 | 登录失败 | 与预期结果一致 | +| 用户登录 | 没有退出当前账号,第二次进入该系统 | 自动登录成功 | 与预期结果一致 | + +### 7.1.2 信息发布模块测试 + +该模块测试主要是验证能否正常发布信息和上传图片及微视频,当没有输入信息时候应当不能点击发送按钮。附件上传前要注意压缩,并且上传后应该在班圈信息中得到正常显示,中间有任何出错需要提示相应错误。而且在6.0以上系统的手机应该动态申请权限。在发布通知或者作业页面,应当发起推送到该班级圈子下的家长手机中。 + +| 测试项目 | 测试方法 | 预期结果 | | 结论 | +| ---- | ---------------------------------------- | ---------------------------------------- | ---- | ------- | +| 信息发布 | 不输入任何文字点击发布 | 发布按钮不能点击 | | 与预期结果一致 | +| 信息发布 | 输入信息点击发布 | 发布成功,班圈显示刷新显示本条内容 | | 与预期结果一致 | +| 信息发布 | 点击图片上传,进入图片选择页面,选择后点击确定返回 | 选择图片后在信息发布页面能显示正常的图片信息,并且首次使用该功能应该弹出申请权限的对话框 | | 与预期结果一致 | +| 信息发布 | 点击微视频上传,进入微视频录制页面,点击上传后返回 | 信息发布页面正常显示该条微视频的缩略图,点击缩略图能正常播放视频,首次使用该功能应该弹出动态申请权限的对话框 | | 与预期结果一致 | +| 信息发布 | 发布信息,查看Fidder抓包情况 | Fidder抓包信息应当显示和接口预期一致 | | 与预期结果一致 | +| 信息发布 | 发布班级通知或者作业的时候,查看Fidder抓包情况和该班级圈子的家长用户手机情况 | Fidder抓包信息应该和接口预期一致,并且该班级圈下的家长应该收到信息推送 | | 与预期结果一致 | + +### 7.1.3 信息交流模块测试 + +该模块测试主要是测试信息能否正常地点赞评论回复,在该功能中,如果本用户之前未点赞(灰色),应当把点赞按钮置为点赞状态(红色),点赞数+1。点击班圈某条信息,可以正常进入到该信息的详情页面,并可以评论,返回后正常显示相关信息。 + +| 信息交流 | 测试方法 | 预期结果 | 结论 | +| ---- | --------- | ------------------------------------- | ------- | +| 信息交流 | 点击班圈的某条信息 | 应该能正常进入详情页面 | 与预期结果一致 | +| 信息交流 | 点击点赞按钮 | 在未点赞的时候应该为灰色,点赞后应该为红色,可以取消点赞,相应数目应该变化 | 与预期结果一致 | +| 信息交流 | 点击评论按钮 | 进入信息详情页面,并且弹出键盘 | 与预期结果一致 | +| 信息交流 | 点击评论的人 | 应该直接开始弹出软键盘,并且置为回复该用户的状态 | 与预期结果一致 | +| 信息交流 | 点击返回 | 如果该条信息详情有所更新,应当提醒班级圈正常显示点赞情况和评论数目情况 | 与预期结果一致 | + +### 7.1.4 即时通讯模块测试 + +即时通讯模块测试主要是测试添加好友,音视频通话,聊天,发送附件,好友列表等能否正常显示,以及APP置于后台能否正常收到离线推送的即时通讯消息。 + +| 即时通讯 | 测试方法 | 预期结果 | 结论 | +| ---- | ------------------------ | ---------------------------------------- | ------- | +| 即时通讯 | 点击消息Tab | 能查看到最近联系人 | 与预期结果一致 | +| 即时通讯 | 点击联系人Tab | 能正常显示联系人相关信息 | 与预期结果一致 | +| 即时通讯 | 点击某条会话或者联系人 | 能正常进入聊天页面,并能正常显示信息和聊天 | 与预期结果一致 | +| 即时通讯 | 点击音视频通话 | 进入音视频通话页面,被呼叫用户应当能正常收到此信息,并可选择挂断,发起者可以收到用户B接受或者拒绝的反馈,若是接受,应当正常进行音视频聊天 | 与预期结果一致 | +| 即时通讯 | 用户B应用置于后台,用户A给用户B发送文本消息 | 用户B手机能收到信息推送 | 与预期结果一致 | +| 即时通讯 | 用户B应用置于后台,用户A向用户B发起音视频呼叫 | 用户B应当直接呼起音视频通话页面,并能选择接受或者拒绝 | 与预期结果一致 | + +## 7.3 性能测试 + +性能测试需要验证APP在各种外界压力下是否能正确响应;在执行单一操作时候的响应时间;重复操作一功能,系统资源占用情况;我们在项目中采用了LeakCanary开源框架,并把它移植到项目中检查内存泄漏情况。并且使用Android内存泄漏分析工具(MemoryAnalyzer)检测内存使用情况,最终通过分析优化了下面两个方面: + +- 图片压缩不要将整个图片以Bitmap读入内存,防止OOM的发生,替换为ExitInterface类获取图片信息,并采用BitmapFatory的decodeXXX方法以及Bitmap的createScaleBitmap进行尺寸压缩,最后再进行质量压缩得以解决 + +- 项目中有些地方采用了static静态对象,持有Context等导致内存久久不能释放,后面替换了ApplicationContext得以解决 + +- 测试过程中发现启动白屏现象较为严重,所以增加闪屏页得以缓解 + +## 7.4 安全测试 + +随着移动互联网的飞速发展,而作为产业模式下的移动平台,自然备受关注,依托此平台的APP的安全性进而成为人们的焦点。所以我对软件权限等进行了细致检查,得到以下结果: + +- 没有任何的泄密权限或者非法访问情况 + +- 没有出现任何的自启动,没有捆绑其他任何软件 + +- 数据加密均正常,不存在泄密危险 + +## 7.5 交叉事件测试 + +交叉事件测试,又叫事件或者冲突测试。意思是当APP在运行中,与此同时被另外的事件干扰,比如接入电话,查看短信后是否会导致APP崩溃或者数据丢失等异常。如果执行干扰的冲突事件后,应用APP依然能正常运行,不会出现崩溃、终端死机或者丢失数据等问题,则视为我们的交叉事件测试通过。 + +在交叉事件测试中,我着重检查了几个方面: + +APP运行时,前台后切换或者横竖屏切换出现了数据的丢失,经过修改后得以解决; + +APP运行时,能正常接收电话和短信; + +运行“爱吖校推”,并不会影响其他功能的使用,依然能正常的查看QQ消息、微信消息等。 + +## 7.6 兼容性测试 + +在Android众多的第三方定制系统的大背景下,各种各样奇葩的兼容性问题一定存在,虽然在我们开发中采用的测试真机是公认最容易出问题的MIUI手机,但依然不能以偏概全,在兼容性测试阶段,我采用腾讯云真机租用做了基本所有定制系统的兼容性测试。在兼容性测试中,我着重处理了: + +Android 7.0 后不能直接通过Uri调用系统相机,检查出问题后,采用了文件FileProvider得以解决。 + +在三星手机的测试中,出现了拍照后旋转问题,最后在代码中通过ExitInterface等操作解决了这个问题。 + +# 8. 结论 + +本次毕业设计针对越来越被看好的“互联网+”教育,着眼于促进教育现代化发展,加强学校与家长的沟通交流。设计过程中采用较多的Design美学理念和动画效果,增加用户粘性。提供推送服务,极大的满足了用户不丢失重要班级信息。社区化的设计,帮助用户群体更好的交流。 + +由于各方面的原因和经验匮乏等问题,本应用的一些细节处理还不那么完美,但我依然会完善下去。开发这款应用,让我学到很多,比如很多当前Android火热的框架,Retrofit、Rx、即时通讯、推送以及图片压缩等,尤其是后台板块的学习,PHP作为当前比较热门的语言,我直接从零学习到一步一步搭建起自己的后台,收获巨大。 + +# 参考文献 + +[1] 明日科技.Android从入门到精通[M].北京:清华大学出版社,2012.9 + +[2] 郭霖.第二行代码[M].北京:清华大学出版社,2016.11 + +[3] 李刚.疯狂Android讲义(第3版)[M].北京:电子工业出版社,2015. + +[4] 郭金尚.Android经典项目案例开发实战宝典[M].北京:清华大学出版社,2013.9 + +[5] 刘金桥. 基于web的贝佳宠物医院管理系统设计与实现 2015-06-03 + +[6] 许瑾.第一次开发Android程序的历程[J]. 科技资讯,2014.29.20 + +[7] 丁丽萍.Android操作系统的安全性分析[J].信息网络安全,2012.3:58-60 + +[8] 王珊.数据库系统概论.北京:电子工业出版社,2015 + +[9] (美)赞德斯彻.深入PHP:面向对象、模式与实践(第3版)[M].机械工业出版社,2009.4 + +[10] 杨宇.PHP典型模块与项目实战大全[M].清华大学出版社,2012.1 + +[11] (美)林恩.贝伊利,迈克尔·莫里森着苏金国,徐阳译O’Reilly:HeadFirst PHP &MySQL(中文版)中国电力出版社2010 386 + +[12] 马千里. 基于安卓手机的“视界”应用程序的设计和实现2016-05-31 \ No newline at end of file diff --git a/文档/API接口文档.docx b/文档/API接口文档.docx new file mode 100644 index 0000000..47895c3 Binary files /dev/null and b/文档/API接口文档.docx differ diff --git a/文档/Android/.gitattributes b/文档/Android/.gitattributes new file mode 100644 index 0000000..bdb0cab --- /dev/null +++ b/文档/Android/.gitattributes @@ -0,0 +1,17 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/文档/Android/app/.gitignore b/文档/Android/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/文档/Android/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/文档/Android/app/app.iml b/文档/Android/app/app.iml new file mode 100644 index 0000000..1ee0580 --- /dev/null +++ b/文档/Android/app/app.iml @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/文档/Android/app/build.gradle b/文档/Android/app/build.gradle new file mode 100644 index 0000000..6d54e05 --- /dev/null +++ b/文档/Android/app/build.gradle @@ -0,0 +1,181 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.2" + + defaultConfig { + applicationId "com.example.nanchen.aiyaschoolpush" + minSdkVersion 16 + targetSdkVersion 22 + versionCode 25 + versionName "2.3.1" + multiDexEnabled true + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + + + release { + // 是否进行dex优化 + zipAlignEnabled false + + // 是否进行混淆 + minifyEnabled false + // 支持删除一些没有用的资源 + shrinkResources false + // 混淆文件位置 + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + + debug { + minifyEnabled false + shrinkResources false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + +// productFlavors { +// android.applicationVariants.all { variant -> +// variant.outputs.each { output -> +// output.outputFile = new File("AiYa-SchoolPush" + "-v" + +// defaultConfig.versionName + ".apk"); +// } +// } +// } + + // 移除lint检查的error + lintOptions { + abortOnError false + } + + dexOptions { + javaMaxHeapSize "4g" + } + + // 使用百度定位需要 + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + } + } + + defaultConfig { + ndk { + abiFilters 'armeabi-v7a' + } + } +} + +repositories { + flatDir { + dirs 'libs' //就是你放aar的目录地址 + } +} + + + + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:25.1.1' + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + // mob message + compile name: 'SMSSDK-2.1.1', ext: 'aar' + compile files('libs/MobTools-2016.0714.1402.jar') + compile files('libs/MobCommons-2016.0714.1402.jar') + // material design + compile 'com.android.support:design:25.1.1' + compile 'com.android.support:cardview-v7:25.1.1' + // photo + compile 'com.squareup.picasso:picasso:2.5.2' + // rx + compile 'io.reactivex:rxjava:1.2.0' + compile 'io.reactivex:rxandroid:1.2.1' + //retrofit + compile 'com.squareup.retrofit2:retrofit:2.1.0' + compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' + compile 'com.google.code.gson:gson:2.7' + compile 'com.squareup.retrofit2:converter-gson:2.1.0' + compile 'com.squareup.okhttp3:okhttp:3.4.1' + // bottom tab + compile 'com.github.armcha:SpaceNavigationView:1.4.1' + // 字母导航栏 + compile 'com.bigkoo:quicksidebar:1.0.3' + compile 'com.timehop.stickyheadersrecyclerview:library:0.4.3@aar' + // 自动轮播 + compile 'com.nineoldandroids:library:2.4.0' + compile 'com.daimajia.slider:library:1.1.5@aar' + // 开源弹出式Toast + compile('de.keyboardsurfer.android.widget:crouton:1.8.5@aar') { + // exclusion is not neccessary, but generally a good idea. + exclude group: 'com.google.android', module: 'support-v4' + } + //炫酷的dialog + compile 'com.nineoldandroids:library:2.4.0' + compile 'com.github.sd6352051.niftydialogeffects:niftydialogeffects:1.0.0@aar' + // 例子动画 + compile 'me.wangyuwei:ParticleView:1.0.5' + // 闪动加载 + compile 'com.elyeproj.libraries:loaderviewlibrary:1.2.1' + // 另一种方式的Dialog弹出方式 + compile 'com.nineoldandroids:library:2.4.0' + compile 'com.github.sd6352051.niftydialogeffects:niftydialogeffects:1.0.0@aar' + // 带记忆功能的搜索框 + compile 'com.github.mancj:MaterialSearchBar:0.3.1' + // 奇特的RecyclerView + compile 'com.marshalchen.ultimaterecyclerview:library:0.7.0' + // 进度条水平方向 + compile 'com.daimajia.numberprogressbar:library:1.2@aar' + // 弹起式日期控件 + compile 'com.philliphsu:bottomsheetpickers:2.0.0' + // 支持下拉刷新和上拉加载的RecyclerView + compile 'com.jcodecraeer:xrecyclerview:1.2.7' + // Material Design风格的输入框开源 + compile 'com.rengwuxian.materialedittext:library:2.1.4' + // 环信集成相关 + compile files('libs/parse-android-1.13.0.jar') + compile files('libs/bolts-tasks-1.4.0.jar') + // LeakCanary + debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5' + releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' + testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' + // multidex + compile 'com.android.support:multidex:1.0.1' + // 网易七鱼客服 + compile 'com.qiyukf.unicorn:unicorn:2.9.0' + compile 'com.alibaba:fastjson:1.2.20' + // 网络访问框架okgo 封装okhttp + compile 'com.lzy.net:okgo:2.1.4' + //版本号使用 + 可以自动引用最新版 + + // 圆形image + compile 'de.hdodenhof:circleimageview:2.1.0' + compile 'org.greenrobot:eventbus:3.0.0' + // 九宫格图片展示和仿微信图片选择 + compile 'com.lzy.widget:ninegridview:0.2.0' + compile 'com.lzy.widget:imagepicker:0.5.5' + //最新版本 + + // material dialog + compile 'com.afollestad.material-dialogs:commons:0.9.1.0' + compile 'com.afollestad.material-dialogs:core:0.9.1.0' + compile project(':easeUI_CN') + compile 'com.mabeijianxi:small-video-record:1.0.8' + compile 'com.baoyz.pullrefreshlayout:library:1.2.0' + compile 'com.github.nanchen2251:CompressHelper:1.0.2' + +} +repositories { + maven { + url 'https://dl.bintray.com/wangyuwei/maven' + } +} + + + + + diff --git a/文档/Android/app/libs/MobCommons-2016.0714.1402.jar b/文档/Android/app/libs/MobCommons-2016.0714.1402.jar new file mode 100644 index 0000000..f63217a Binary files /dev/null and b/文档/Android/app/libs/MobCommons-2016.0714.1402.jar differ diff --git a/文档/Android/app/libs/MobTools-2016.0714.1402.jar b/文档/Android/app/libs/MobTools-2016.0714.1402.jar new file mode 100644 index 0000000..524b9e5 Binary files /dev/null and b/文档/Android/app/libs/MobTools-2016.0714.1402.jar differ diff --git a/文档/Android/app/libs/SMSSDK-2.1.1.aar b/文档/Android/app/libs/SMSSDK-2.1.1.aar new file mode 100644 index 0000000..173e9eb Binary files /dev/null and b/文档/Android/app/libs/SMSSDK-2.1.1.aar differ diff --git a/文档/Android/app/libs/bolts-tasks-1.4.0.jar b/文档/Android/app/libs/bolts-tasks-1.4.0.jar new file mode 100644 index 0000000..d4118d6 Binary files /dev/null and b/文档/Android/app/libs/bolts-tasks-1.4.0.jar differ diff --git a/文档/Android/app/libs/mobAPI-1.0.6.jar b/文档/Android/app/libs/mobAPI-1.0.6.jar new file mode 100644 index 0000000..991eaef Binary files /dev/null and b/文档/Android/app/libs/mobAPI-1.0.6.jar differ diff --git a/文档/Android/app/libs/parse-android-1.13.0.jar b/文档/Android/app/libs/parse-android-1.13.0.jar new file mode 100644 index 0000000..b250b4d Binary files /dev/null and b/文档/Android/app/libs/parse-android-1.13.0.jar differ diff --git a/文档/Android/app/proguard-rules.pro b/文档/Android/app/proguard-rules.pro new file mode 100644 index 0000000..cba9a27 --- /dev/null +++ b/文档/Android/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Users\Administrator\AppData\Local\Android\Sdk1\Sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/文档/Android/app/src/androidTest/java/com/example/nanchen/aiyaschoolpush/ApplicationTest.java b/文档/Android/app/src/androidTest/java/com/example/nanchen/aiyaschoolpush/ApplicationTest.java new file mode 100644 index 0000000..1fe84cd --- /dev/null +++ b/文档/Android/app/src/androidTest/java/com/example/nanchen/aiyaschoolpush/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.example.nanchen.aiyaschoolpush; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/文档/Android/app/src/debug/AndroidManifest.xml b/文档/Android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..7b85e1c --- /dev/null +++ b/文档/Android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/文档/Android/app/src/main/AndroidManifest.xml b/文档/Android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2443e8b --- /dev/null +++ b/文档/Android/app/src/main/AndroidManifest.xml @@ -0,0 +1,454 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/文档/Android/app/src/main/assets/fonts/icomoon.ttf b/文档/Android/app/src/main/assets/fonts/icomoon.ttf new file mode 100644 index 0000000..6c799e3 Binary files /dev/null and b/文档/Android/app/src/main/assets/fonts/icomoon.ttf differ diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/App.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/App.java new file mode 100644 index 0000000..3df15e5 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/App.java @@ -0,0 +1,209 @@ +package com.example.nanchen.aiyaschoolpush; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.Application; +import android.content.Context; +import android.os.Environment; +import android.os.Process; +import android.support.multidex.MultiDex; +import android.util.Log; + +import com.example.nanchen.aiyaschoolpush.helper.DemoHelper; +import com.example.nanchen.aiyaschoolpush.helper.QiYuCloudServerHelper; +import com.lzy.imagepicker.ImagePicker; +import com.lzy.imagepicker.view.CropImageView; +import com.lzy.ninegrid.NineGridView; +import com.lzy.okgo.OkGo; +import com.lzy.okgo.cookie.store.PersistentCookieStore; +import com.mob.mobapi.MobAPI; +import com.squareup.leakcanary.LeakCanary; +import com.xiaomi.channel.commonutils.logger.LoggerInterface; +import com.xiaomi.mipush.sdk.Logger; +import com.xiaomi.mipush.sdk.MiPushClient; + +import java.io.File; +import java.util.List; +import java.util.logging.Level; + +import cn.smssdk.SMSSDK; +import mabeijianxi.camera.VCamera; +import mabeijianxi.camera.util.DeviceUtils; + + +/** + * 启动的Application + * + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush + * @date 2016/09/08 15:51 + */ +public class App extends Application { + + private static final String MSG_APP_KEY = "16faeb1248a89";// 短信验证的app_key + private static final String MSG_APP_SECRET = "20d994397ced27b44b48ce80956a6f9d";// 短信验证的app_secret + private static final String MOB_APP_KEY = "1730bae762bbc";// MobApi的应用app_key + private static final String TAG = "App"; + + private static final String MIPUSH_APP_KEY = "5681752153371"; // 小米推送App_key + private static final String MIPUSH_APP_ID = "2882303761517521371"; //小米推送app_id + + private static App app; + + public static App getInstance() { + return app; + } + + + @Override + public void onCreate() { + super.onCreate(); + app = this; + + + // LeakCanary + if (LeakCanary.isInAnalyzerProcess(this)) { + // This process is dedicated to LeakCanary for heap analysis. + // You should not init your app in this process. + return; + } + LeakCanary.install(this); + + + // 初始化小米推送相关 + initMiPush(); + + // 初始化短信验证SDK + SMSSDK.initSDK(this, MSG_APP_KEY, MSG_APP_SECRET); + + // 初始化MobApiSDK + MobAPI.initSDK(getApplicationContext(), MOB_APP_KEY); + + //init demo helper + DemoHelper.getInstance().init(App.getAppContext()); + + // 七鱼客服初始化 + QiYuCloudServerHelper.initCloudServer(this); + + // OkGo初始化 + OkGo.init(this); + OkGo.getInstance().debug("okgo", Level.WARNING,true) + .setConnectTimeout(20000) //全局的连接超时时间 + .setReadTimeOut(20000) //全局的读取超时时间 + .setWriteTimeOut(20000) //全局的写入超时时间 + .setCookieStore(new PersistentCookieStore()); + + // NineGridView的图片加载方式初始化 + NineGridView.setImageLoader(new PicassoImageLoader()); + initImagePicker(); // 初始化ImagePicker + + + // 小视频 +// try { +// // 不知道小视频为什么不可用 +// initSmallVideo(this); +// } catch (Exception e) { +// e.printStackTrace(); +// throw new VideoException("当前手机暂不支持微视频"); +// } + + } + + public static void initSmallVideo(Context context){ + // 设置拍摄视频缓存路径 + File dcim = Environment + .getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM); + if (DeviceUtils.isZte()) { + if (dcim.exists()) { + VCamera.setVideoCachePath(dcim + "/mabeijianxi/"); + } else { + VCamera.setVideoCachePath(dcim.getPath().replace("/sdcard/", + "/sdcard-ext/") + + "/mabeijianxi/"); + } + } else { + VCamera.setVideoCachePath(dcim + "/mabeijianxi/"); + } + VCamera.setDebugMode(true); + try{ + VCamera.initialize(context); + } catch (Exception e){ + e.printStackTrace(); + throw new VideoException("当前手机暂不支持微视频"); + } + } + + private void initImagePicker() { + ImagePicker imagePicker = ImagePicker.getInstance(); + imagePicker.setImageLoader(new GlideImageLoader()); //设置图片加载器 + imagePicker.setShowCamera(true); //显示拍照按钮 + imagePicker.setCrop(true); //允许裁剪(单选才有效) + imagePicker.setSaveRectangle(true); //是否按矩形区域保存 + imagePicker.setSelectLimit(9); //选中数量限制 + imagePicker.setStyle(CropImageView.Style.RECTANGLE); //裁剪框的形状 + imagePicker.setFocusWidth(800); //裁剪框的宽度。单位像素(圆形自动取宽高最小值) + imagePicker.setFocusHeight(800); //裁剪框的高度。单位像素(圆形自动取宽高最小值) + imagePicker.setOutPutX(1000);//保存文件的宽度。单位像素 + imagePicker.setOutPutY(1000);//保存文件的高度。单位像素 + } + + + /** + * 初始化小米推送相关 + */ + private void initMiPush() { + //初始化push推送服务 + if (shouldInit()) { + MiPushClient.registerPush(this, MIPUSH_APP_ID, MIPUSH_APP_KEY); + } + + LoggerInterface newLogger = new LoggerInterface() { + @Override + public void setTag(String tag) { + // ignore + } + + @Override + public void log(String content, Throwable t) { + Log.d(TAG, content, t); + } + + @Override + public void log(String content) { + Log.d(TAG, content); + } + }; + Logger.setLogger(this, newLogger); + + + } + + private boolean shouldInit() { + ActivityManager am = ((ActivityManager) getSystemService(Context.ACTIVITY_SERVICE)); + List processInfos = am.getRunningAppProcesses(); + String mainProcessName = getPackageName(); + int myPid = Process.myPid(); + for (RunningAppProcessInfo info : processInfos) { + if (info.pid == myPid && mainProcessName.equals(info.processName)) { + return true; + } + } + return false; + } + + /** + * 获取Application Context + */ + public static Context getAppContext() { + return app != null ? app.getApplicationContext() : null; + } + + public static String currentUserNick = ""; + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + MultiDex.install(this); + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/AppService.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/AppService.java new file mode 100644 index 0000000..b83edee --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/AppService.java @@ -0,0 +1,303 @@ +package com.example.nanchen.aiyaschoolpush; + +import com.example.nanchen.aiyaschoolpush.config.Consts; +import com.example.nanchen.aiyaschoolpush.model.PraiseModel; +import com.example.nanchen.aiyaschoolpush.model.User; +import com.example.nanchen.aiyaschoolpush.model.info.InfoModel; +import com.example.nanchen.aiyaschoolpush.model.info.UserModel; +import com.example.nanchen.aiyaschoolpush.net.okgo.JsonCallback; +import com.example.nanchen.aiyaschoolpush.net.okgo.LslResponse; +import com.lzy.okgo.OkGo; +import com.lzy.okgo.callback.StringCallback; +import com.lzy.okgo.request.PostRequest; + +import java.io.File; +import java.util.HashMap; +import java.util.List; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.api + * @date 2016/11/09 11:19 + */ + +public class AppService { + private static final String TAG = "AppService"; + + private static AppService instance; + private User mCurrentUser; + + + private AppService(){ + } + + public static AppService getInstance(){ + if (instance == null){ + instance = new AppService(); + } + return instance; + } + + /** + * 退出登录时重置此类 + */ + public static void resetInstance(){ + instance = null; + } + + /** + * 获取当前登录的用户 + * @return 当前用户 + */ + public User getCurrentUser(){ + return mCurrentUser; + } + + public void setCurrentUser(User user){ + this.mCurrentUser = user; + } + + + + /*************** 用户系统 Begin ******************/ + + + /** + * 用户手机号验证是否已经注册 + * @param mobile 手机号 + * @param callback 回调 + */ + public void isUsableMobileAsync(String mobile, JsonCallback> callback){ + String url = Consts.API_SERVICE_HOST+"/user/usable_mobile.php?mobile="+mobile; + OkGo.get(url).execute(callback); + } + + + /** + * 用户注册 + * @param username 用户名 + * @param password 密码 + * @param nickname 昵称 + * @param birthday 生日 + * @param callback 回调 + */ + public void registerAsync(String username,String password,String nickname,String birthday,String iconUrl,JsonCallback> callback){ + String url = Consts.API_SERVICE_HOST + "/user/register.php"; + HashMap postParams = new HashMap<>(); + postParams.put("username",username); + postParams.put("password",password); + postParams.put("nickname",nickname); + postParams.put("birthday",birthday); + postParams.put("avatar",iconUrl); + OkGo.post(url).params(postParams).execute(callback); + } + + /** + * 异步用户登录 + * @param username 用户名 + * @param password 用户密码 + * @param callback 回调 + */ + public void loginAsync(String username,String password,JsonCallback> callback){ + String url = Consts.API_SERVICE_HOST + "/user/login.php"; + HashMap postParams = new HashMap<>(); + postParams.put("username",username); + postParams.put("password",password); + OkGo.post(url).params(postParams).execute(callback); + } + + + + + /** + * 异步用户修改密码 + * @param username 用户名 + * @param password 新密码 + * @param callback 回调 + */ + public void resetPwdAsync(String username,String password,JsonCallback> callback){ + String url = Consts.API_SERVICE_HOST + "/user/reset_pwd.php"; + HashMap postParams = new HashMap<>(); + postParams.put("username",username); + postParams.put("password",password); + OkGo.post(url).params(postParams).execute(callback); + } + + /** + * 异步用户头像上传 + * @param file 文件 + * @param callback 回调 + */ + public void upLoadAvatarAsync(File file,JsonCallback> callback){ + String url = Consts.API_SERVICE_HOST + "/user/avatar.php"; + OkGo.post(url).params("avatar",file,file.getName()).execute(callback); + } + + /** + * 异步用户头像上传 + * @param file 文件 + * @param callback 回调 返回json字符串,只是为了测试 + */ + public void upLoadAvatarAsync(File file, StringCallback callback){ + String url = Consts.API_SERVICE_HOST + "/user/avatar.php"; + OkGo.post(url).params("avatar",file).execute(callback); + } + + /** + * 更新用户头像信息 + * + * @param username 用户名 + * @param iconUrl 头像地址 + * @param type 传递类型 + * @param callback 回调 + */ + public void updateAvatarUrlAsync(String username,String iconUrl,int type,JsonCallback> callback){ + String url = Consts.API_SERVICE_HOST+"/user/update_avatar.php?username="+username + +"&iconUrl="+iconUrl+"&type="+type; + OkGo.get(url).execute(callback); + } + + + /** + * 更新用户信息 + * @param username 用户名 + * @param action 更新项 + * @param value 更新值 + * @param callback 回调 + */ + public void updateUserInfoAsync(String username,String action,String value,JsonCallback> callback){ + String url = Consts.API_SERVICE_HOST+"/user/update_user.php?username="+username + +"&action="+action+"&value="+value; + OkGo.get(url).execute(callback); + } + + + /*************** 用户系统 End ******************/ + + + /*************** 信息系統 Begin ******************/ + + + /** + * 异步获取信息 + * @param classId 班级id + * @param username 用户名 用于返回用户是否赞了主贴 + * @param infoType 信息类型 1 公告 2 作业 3 动态 + * @param start 信息从第几行开始提取 + * @param count 信息数目 这里一次获取10条 需要更多获取联系后台 + * @param callback 回调 + */ + public void getNoticeAsync(int classId,String username, int infoType,int start, int count,int lastId, JsonCallback>> callback){ + String url = Consts.API_SERVICE_HOST+"/info/info_main.php?classId="+classId+ + "&username="+username+"&infoType="+infoType+"&start="+start+"&count="+count+"&lastId="+lastId; + OkGo.get(url).execute(callback); + } + + /** + * 异步获取发布信息的用户信息 + * @param username 用户名 + * @param callback 回调 + */ + public void getUserInfoAsync(String username, JsonCallback> callback){ + String url = Consts.API_SERVICE_HOST+"/info/get_user.php?username="+username; + OkGo.get(url).execute(callback); + } + + + /** + * 异步插入数据到评论 + * @param mainId 信息表条目id + * @param username 发布人id + * @param content 发布内容 + * @param reply 回复的人的id + * @param callback 回调 + */ + public void insertCommentAsync(int mainId, String username, String content, + String reply, JsonCallback> callback){ + String url = Consts.API_SERVICE_HOST+"/info/insert_comment.php"; + HashMap postParams = new HashMap<>(); + postParams.put("mainId",mainId+""); + postParams.put("username",username); + postParams.put("content",content); + postParams.put("reply",reply); + OkGo.post(url).params(postParams).execute(callback); + } + + /** + * 更新点赞的信息 + * @param mainId 主贴的id + * @param username 用户名 - 用户只能删除自己发布的信息 + * @param callback 回调接口 + */ + public void updatePraiseAsync(int mainId, String username, JsonCallback> callback){ + String url = Consts.API_SERVICE_HOST+"/info/praise.php?mainId="+mainId+"&username="+username; + OkGo.get(url).execute(callback); + } + + + /** + * 异步发送消息给服务器 + * @param classId 班级id + * @param username 发布人用户名 + * @param infoType 信息类型 1 公告 2 作业 3 动态 + * @param content 发布内容 + * @param picUrls 图片地址 + * @param callback 回调 + */ + public void addMainInfoAsync(int classId, String username, int infoType, String content, List picUrls,boolean isVideo, JsonCallback> callback){ + String url = Consts.API_SERVICE_HOST+"/info/add_main.php"; + HashMap postParams = new HashMap<>(); + postParams.put("classId",classId+""); + postParams.put("username",username); + postParams.put("infoType",infoType+""); + postParams.put("content",content); + postParams.put("picCount",picUrls.size()+""); + if (isVideo){ + postParams.put("type","video"); + postParams.put("picUrl0","/info/video/"+picUrls.get(0)); + postParams.put("picUrl1","/info/pic/"+picUrls.get(1)); + }else{ + postParams.put("type","pic"); + for (int i = 0; i < picUrls.size(); i++) { + postParams.put("picUrl"+i,"/info/pic/"+picUrls.get(i)); + } + } + OkGo.post(url).params(postParams).execute(callback); + } + + /** + * 异步提醒服务器推送相关消息给他人 + * + * + * @param classId 班级id + * @param infoType 信息类型 + * @param callback 回调 + */ + public void sendMsgToOthersAsync(int classId,int infoType,JsonCallback> callback){ + String url = Consts.API_SERVICE_HOST+"/android_example.php?classId="+classId+"&infoType="+infoType; + OkGo.get(url).execute(callback); + } + + + /** + * 附件上传 + * @param files 文件集合 + * @param callback 回调 + */ + public void upLoadFileAsync(List files,JsonCallback> callback){ + String url = Consts.API_SERVICE_HOST + "/info/attachment.php"; +// OkGo.post(url).params("size",files.size()).addFileParams("files",files).execute(callback); +// String url = Consts.API_SERVICE_HOST + "/user/avatar.php"; + PostRequest postRequest = OkGo.post(url); + for (int i = 0; i < files.size(); i++) { + postRequest.params("file"+i,files.get(i),files.get(i).getName()); + } + postRequest.params("size",files.size()); + postRequest.execute(callback); + } + + + /*************** 信息系統 End ******************/ + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/CropOption.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/CropOption.java new file mode 100644 index 0000000..427b48d --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/CropOption.java @@ -0,0 +1,17 @@ +package com.example.nanchen.aiyaschoolpush; + +import android.content.Intent; +import android.graphics.drawable.Drawable; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush + * @date 2016/09/28 14:44 + */ + +public class CropOption { + public CharSequence title; + public Drawable icon; + public Intent appIntent; +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/GlideImageLoader.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/GlideImageLoader.java new file mode 100644 index 0000000..0129211 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/GlideImageLoader.java @@ -0,0 +1,34 @@ +package com.example.nanchen.aiyaschoolpush; + +import android.app.Activity; +import android.net.Uri; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.lzy.imagepicker.loader.ImageLoader; + +import java.io.File; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush + * @date 2016/11/29 09:43 + */ + +public class GlideImageLoader implements ImageLoader { + @Override + public void displayImage(Activity activity, String path, ImageView imageView, int width, int height) { + Glide.with(activity) //配置上下文 + .load(Uri.fromFile(new File(path))) //设置图片路径(fix #8,文件名包含%符号 无法识别和显示) + .error(R.mipmap.default_image) //设置错误图片 + .placeholder(R.mipmap.default_image) //设置占位图片 + .diskCacheStrategy(DiskCacheStrategy.ALL)//缓存全尺寸 + .into(imageView); + } + + @Override + public void clearMemoryCache() { + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/PicassoImageLoader.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/PicassoImageLoader.java new file mode 100644 index 0000000..06fe579 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/PicassoImageLoader.java @@ -0,0 +1,30 @@ +package com.example.nanchen.aiyaschoolpush; + +import android.content.Context; +import android.graphics.Bitmap; +import android.widget.ImageView; + +import com.lzy.ninegrid.NineGridView; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush + * @date 2016/11/29 09:43 + */ + +public class PicassoImageLoader implements NineGridView.ImageLoader { + + @Override + public void onDisplayImage(Context context, ImageView imageView, String url) { + com.squareup.picasso.Picasso.with(context).load(url)// + .placeholder(R.drawable.ic_default_image)// + .error(R.drawable.ic_default_image)// + .into(imageView); + } + + @Override + public Bitmap getCacheImage(String url) { + return null; + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/SendSmallVideoActivity.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/SendSmallVideoActivity.java new file mode 100644 index 0000000..380339f --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/SendSmallVideoActivity.java @@ -0,0 +1,312 @@ +package com.example.nanchen.aiyaschoolpush; + +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import com.example.nanchen.aiyaschoolpush.config.AddConfig; +import com.example.nanchen.aiyaschoolpush.helper.event.CommunityEvent; +import com.example.nanchen.aiyaschoolpush.helper.event.HomeworkEvent; +import com.example.nanchen.aiyaschoolpush.helper.event.NoticeEvent; +import com.example.nanchen.aiyaschoolpush.model.User; +import com.example.nanchen.aiyaschoolpush.model.info.InfoModel; +import com.example.nanchen.aiyaschoolpush.model.info.InfoType; +import com.example.nanchen.aiyaschoolpush.net.okgo.JsonCallback; +import com.example.nanchen.aiyaschoolpush.net.okgo.LslResponse; +import com.example.nanchen.aiyaschoolpush.ui.activity.ActivityBase; +import com.example.nanchen.aiyaschoolpush.ui.view.TitleView; +import com.example.nanchen.aiyaschoolpush.ui.view.WavyLineView; +import com.example.nanchen.aiyaschoolpush.utils.ScreenUtil; +import com.example.nanchen.aiyaschoolpush.utils.UIUtil; + +import org.greenrobot.eventbus.EventBus; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import mabeijianxi.camera.MediaRecorderActivity; +import okhttp3.Call; +import okhttp3.Response; + + +public class SendSmallVideoActivity extends ActivityBase implements View.OnClickListener { + + private static final String TAG = "SendSmallVideoActivity"; + private String videoUri; + private TextView tv_send; + private TextView tv_cancel; + private String videoScreenshot; + private ImageView iv_video_screenshot; + private EditText et_send_content; + + private AlertDialog dialog; + private TitleView mTitleBar; + private int infoType; + private WavyLineView mWavyLine; + private EditText mEditText; + private List mFiles; + private List mSmallUrls; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); +// setContentView(R.layout.smallvideo_text_edit_activity); + initView(); + initData(); + initEvent(); + } + + private void initEvent() { +// tv_cancel.setOnClickListener(this); +// tv_send.setOnClickListener(this); +// et_send_content.setOnClickListener(this); + iv_video_screenshot.setOnClickListener(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mFiles != null){ + mFiles.clear(); + mFiles = null; + } + if (mSmallUrls != null){ + mSmallUrls.clear(); + mSmallUrls = null; + } + } + + private void initData() { + Intent intent = getIntent(); + videoUri = intent.getStringExtra(MediaRecorderActivity.VIDEO_URI); + videoScreenshot = intent.getStringExtra(MediaRecorderActivity.VIDEO_SCREENSHOT); + Bitmap bitmap = BitmapFactory.decodeFile(videoScreenshot); + iv_video_screenshot.setImageBitmap(bitmap); + mFiles = new ArrayList<>(); + mSmallUrls = new ArrayList<>(); + } + + private void initView() { + setContentView(R.layout.activity_release); + + +// tv_cancel = (TextView) findViewById(R.id.tv_cancel); +// tv_send = (TextView) findViewById(R.id.tv_send); +// et_send_content = (EditText) findViewById(R.id.et_send_content); + iv_video_screenshot = (ImageView) findViewById(R.id.iv_video_screenshot); + iv_video_screenshot.setVisibility(View.VISIBLE); + +// mTitleBar = (TitleView) findViewById(R.id.send_video_titleBar); + mTitleBar = (TitleView) findViewById(R.id.release_title); + mTitleBar.setLeftButtonAsFinish(this); + + SharedPreferences sp = getSharedPreferences("send.tmp",MODE_PRIVATE); + + String mFrom = sp.getString("infoType",""); + String content = sp.getString("content",""); + switch (mFrom) { + case AddConfig.NOTICE: + mTitleBar.setTitle("发布公告"); + infoType = InfoType.NOTICE; + break; + case AddConfig.HOMEWORK: + mTitleBar.setTitle("发布作业"); + infoType = InfoType.HOMEWORK; + break; + default: + mTitleBar.setTitle("发布动态"); + infoType = InfoType.COMMUNITY; + break; + } + mTitleBar.changeRightButtonTextColor(getResources().getColor(R.color.white3)); + mTitleBar.setRightButtonText(getResources().getString(R.string.send_back_right)); + mTitleBar.setRightButtonTextSize(25); + mTitleBar.setFixRightButtonPadingTop(); + mTitleBar.setRightButtonOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + uploadFile(); + } + }); + + + + // 波浪线设置 + mWavyLine = (WavyLineView) findViewById(R.id.release_wavyLine); + int initStrokeWidth = 1; + int initAmplitude = 5; + float initPeriod = (float) (2 * Math.PI / 60); + mWavyLine.setPeriod(initPeriod); + mWavyLine.setAmplitude(initAmplitude); + mWavyLine.setStrokeWidth(ScreenUtil.dp2px(initStrokeWidth)); + + + mEditText = (EditText) findViewById(R.id.release_edit); + + if (TextUtils.isEmpty(content) || content.equals("")){// 如果用户之前没有输入信息,则不设置输入框 + return; + } + mEditText.setText(content); + } + + private void uploadFile() { + + final String content = mEditText.getText().toString().trim(); + if (TextUtils.isEmpty(content)) { + UIUtil.showToast("发布内容不能为空!"); + return; + } + File file = new File(videoUri); + File file1 = new File(videoScreenshot); + mFiles.add(file); + mFiles.add(file1); + showLoading(this); + AppService.getInstance().upLoadFileAsync(mFiles,new JsonCallback>() { + @Override + public void onSuccess(LslResponse userLslResponse, Call call, Response response) { + if (userLslResponse.code == LslResponse.RESPONSE_OK) { + UIUtil.showToast("视频上传成功"); + Log.e(TAG, "视频上传成功"); + for (int i = 0; i < mFiles.size(); i++) { + Log.e(TAG, "onSuccess: "+i+":"+mFiles.get(i).getName() ); + } + } else { + UIUtil.showToast("视频上传失败"); + Log.e(TAG, "视频上传失败"); + if (!SendSmallVideoActivity.this.isFinishing()) { + stopLoading(); + return; + } + } + sendInfo(); + } + }); + } + + private void sendInfo() { + final String content = mEditText.getText().toString().trim(); + if (TextUtils.isEmpty(content)) { + UIUtil.showToast("发布内容不能为空!"); + stopLoading(); + return; + } + if(AppService.getInstance().getCurrentUser() == null){ + UIUtil.showToast("未知错误,请重新登录后操作"); + stopLoading(); + return; + } +// mSmallUrls.add(videoScreenshot); +// mSmallUrls.add(videoUri); + for (int i = 0; i < mFiles.size(); i++) { + mSmallUrls.add(mFiles.get(i).getName()); + } + final int classId = AppService.getInstance().getCurrentUser().classid; + String username = AppService.getInstance().getCurrentUser().username; + AppService.getInstance().addMainInfoAsync(classId, username, infoType, content, mSmallUrls, true,new JsonCallback>() { + + @Override + public void onSuccess(LslResponse infoModelLslResponse, Call call, Response response) { + if (infoModelLslResponse.code == LslResponse.RESPONSE_OK) { + UIUtil.showToast("发布信息成功!"); + + Log.e(TAG, "onSuccess: data:"+infoModelLslResponse.data.videoUrl.get(0).toString() ); + // 只有公告和作业才发布推送 + if (infoType == 1 || infoType == 2){ + sendMsgToOthers(classId,infoType); + } + + Log.e(TAG, infoType + ""); + if (infoType == InfoType.NOTICE) { + EventBus.getDefault().post(new NoticeEvent(infoModelLslResponse.data)); + Log.e(TAG, "通知发起"); + } else if (infoType == InfoType.HOMEWORK) { + EventBus.getDefault().post(new HomeworkEvent(infoModelLslResponse.data)); + Log.e(TAG, "作业发起"); + } else { + EventBus.getDefault().post(new CommunityEvent(infoModelLslResponse.data)); + Log.e(TAG, "社区发起"); + } + if (!SendSmallVideoActivity.this.isFinishing()) { + stopLoading(); + } + SendSmallVideoActivity.this.finish(); + } else { + UIUtil.showToast("发布信息失败,请稍后再试!"); + if (!SendSmallVideoActivity.this.isFinishing()) { + stopLoading(); + } + } + } + }); + } + + private void sendMsgToOthers(int classId,int infoType) { + AppService.getInstance().sendMsgToOthersAsync(classId,infoType, new JsonCallback>() { + @Override + public void onSuccess(LslResponse objectLslResponse, Call call, Response response) { + Log.e(TAG,objectLslResponse.msg); + } + }); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { +// case R.id.tv_cancel: +// hesitate(); +// break; +// case R.id.tv_send: +// break; + case R.id.iv_video_screenshot: + startActivity(new Intent(this, VideoPlayerActivity.class).putExtra( + "path", videoUri)); + break; + } + } + + + @Override + public void onBackPressed() { + hesitate(); + } + + private void hesitate() { + if (dialog == null) { + dialog = new AlertDialog.Builder(this) + .setTitle(R.string.hint) + .setMessage(R.string.record_camera_exit_dialog_message) + .setNegativeButton( + R.string.record_camera_cancel_dialog_yes, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + finish(); + +// FileUtils.deleteDir(getIntent().getStringExtra(MediaRecorderActivity.OUTPUT_DIRECTORY)); + + } + + }) + .setPositiveButton(R.string.record_camera_cancel_dialog_no, + null).setCancelable(false).show(); + } else { + dialog.show(); + } + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/VideoException.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/VideoException.java new file mode 100644 index 0000000..2d65d22 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/VideoException.java @@ -0,0 +1,20 @@ +package com.example.nanchen.aiyaschoolpush; + +import com.example.nanchen.aiyaschoolpush.utils.UIUtil; + +/** + * 自定义异常类 + * + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush + * @date 2016/12/15 17:07 + */ + +public class VideoException extends RuntimeException { + + public VideoException(String desc) { + UIUtil.showToast(desc); + + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/VideoPlayerActivity.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/VideoPlayerActivity.java new file mode 100644 index 0000000..d823e9d --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/VideoPlayerActivity.java @@ -0,0 +1,217 @@ +package com.example.nanchen.aiyaschoolpush; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnErrorListener; +import android.media.MediaPlayer.OnInfoListener; +import android.media.MediaPlayer.OnPreparedListener; +import android.os.Build; +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.WindowManager; + +import com.example.nanchen.aiyaschoolpush.ui.activity.ActivityBase; + +import mabeijianxi.camera.MediaRecorderBase; +import mabeijianxi.camera.util.DeviceUtils; +import mabeijianxi.camera.util.StringUtils; +import mabeijianxi.camera.views.SurfaceVideoView; + + +/** + * 通用单独播放界面 + * + * @author tangjun + */ +public class VideoPlayerActivity extends ActivityBase implements + SurfaceVideoView.OnPlayStateListener, OnErrorListener, + OnPreparedListener, OnClickListener, OnCompletionListener, + OnInfoListener { + + /** + * 播放控件 + */ + private SurfaceVideoView mVideoView; + /** + * 暂停按钮 + */ + private View mPlayerStatus; + private View mLoading; + + /** + * 播放路径 + */ + private String mPath; + /** + * 是否需要回复播放 + */ + private boolean mNeedResume; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // 防止锁屏 + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + mPath = getIntent().getStringExtra("path"); + if (StringUtils.isEmpty(mPath)) { + finish(); + return; + } + + setContentView(R.layout.activity_video_player); + mVideoView = (SurfaceVideoView) findViewById(R.id.videoview); + + int screenWidth = getScreenWidth(this); + int videoHight = (int) (screenWidth / (MediaRecorderBase.SMALL_VIDEO_WIDTH / (MediaRecorderBase.SMALL_VIDEO_HEIGHT * 1.0f))); + mVideoView.getLayoutParams().height = videoHight; + mVideoView.requestLayout(); + + mPlayerStatus = findViewById(R.id.play_status); + mLoading = findViewById(R.id.loading); + + mVideoView.setOnPreparedListener(this); + mVideoView.setOnPlayStateListener(this); + mVideoView.setOnErrorListener(this); + mVideoView.setOnClickListener(this); + mVideoView.setOnInfoListener(this); + mVideoView.setOnCompletionListener(this); + +// mVideoView.getLayoutParams().height = DeviceUtils.getScreenWidth(this); + + findViewById(R.id.root).setOnClickListener(this); + mVideoView.setVideoPath(mPath); + } + + public int getScreenWidth(Activity context) { + DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + context.getWindowManager().getDefaultDisplay().getMetrics(mDisplayMetrics); + int W = mDisplayMetrics.widthPixels; + return W; + } + + @Override + public void onResume() { + super.onResume(); + if (mVideoView != null && mNeedResume) { + mNeedResume = false; + if (mVideoView.isRelease()) + mVideoView.reOpen(); + else + mVideoView.start(); + } + } + + @Override + public void onPause() { + super.onPause(); + if (mVideoView != null) { + if (mVideoView.isPlaying()) { + mNeedResume = true; + mVideoView.pause(); + } + } + } + + @Override + protected void onDestroy() { + if (mVideoView != null) { + mVideoView.release(); + mVideoView = null; + } + super.onDestroy(); + } + + @Override + public void onPrepared(MediaPlayer mp) { + mVideoView.setVolume(SurfaceVideoView.getSystemVolumn(this)); + mVideoView.start(); + // new Handler().postDelayed(new Runnable() { + // + // @SuppressWarnings("deprecation") + // @Override + // public void run() { + // if (DeviceUtils.hasJellyBean()) { + // mVideoView.setBackground(null); + // } else { + // mVideoView.setBackgroundDrawable(null); + // } + // } + // }, 300); + mLoading.setVisibility(View.GONE); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + switch (event.getKeyCode()) {// 跟随系统音量走 + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_VOLUME_UP: + mVideoView.dispatchKeyEvent(this, event); + break; + } + return super.dispatchKeyEvent(event); + } + + @Override + public void onStateChanged(boolean isPlaying) { + mPlayerStatus.setVisibility(isPlaying ? View.GONE : View.VISIBLE); + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + if (!isFinishing()) { + // 播放失败 + } + finish(); + return false; + + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.videoview: + case R.id.root: + finish(); + break; + } + } + + @Override + public void onCompletion(MediaPlayer mp) { + if (!isFinishing()) + mVideoView.reOpen(); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + @Override + public boolean onInfo(MediaPlayer mp, int what, int extra) { + switch (what) { + case MediaPlayer.MEDIA_INFO_BAD_INTERLEAVING: + // 音频和视频数据不正确 + break; + case MediaPlayer.MEDIA_INFO_BUFFERING_START: + if (!isFinishing()) + mVideoView.pause(); + break; + case MediaPlayer.MEDIA_INFO_BUFFERING_END: + if (!isFinishing()) + mVideoView.start(); + break; + case MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START: + if (DeviceUtils.hasJellyBean()) { + mVideoView.setBackground(null); + } else { + mVideoView.setBackgroundDrawable(null); + } + break; + } + return false; + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/CommonAdapter.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/CommonAdapter.java new file mode 100644 index 0000000..4b96f0b --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/CommonAdapter.java @@ -0,0 +1,59 @@ +package com.example.nanchen.aiyaschoolpush.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +import java.util.List; + +/** + * 万能适配器,对ListView 和GridView等 + * + * @author nanchen + * @date 2016-7-29 14:17:28 + */ +public abstract class CommonAdapter extends BaseAdapter { + private Context context; + private List list; + private LayoutInflater inflater; + private int itemLayoutId; + + + public CommonAdapter(Context context, List list, int itemLayoutId) { + this.context = context; + this.list = list; + this.itemLayoutId = itemLayoutId; + inflater = LayoutInflater.from(context); + } + + @Override + public int getCount() { + return list == null ? 0 : list.size(); + } + + @Override + public T getItem(int position) { + return list.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder = getViewHolder(position,convertView,parent); + convert(holder,getItem(position)); + return holder.getConvertView(); + } + + public abstract void convert(ViewHolder holder,T item); + + private ViewHolder getViewHolder(int position,View convertView,ViewGroup parent){ + return ViewHolder.get(context,convertView,parent,itemLayoutId,position); + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/CommonRecyclerAdapter.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/CommonRecyclerAdapter.java new file mode 100644 index 0000000..b55233c --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/CommonRecyclerAdapter.java @@ -0,0 +1,149 @@ +package com.example.nanchen.aiyaschoolpush.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.List; + +/** + * @author nanchen + * @fileName ischool + * @packageName com.idtechinfo.shouxiner.adapter.common + * @date 2016/09/02 15:47 + */ +public abstract class CommonRecyclerAdapter extends RecyclerView.Adapter { + private Context context;//上下文 + private List list;//数据源 + private LayoutInflater inflater;//布局器 + private int itemLayoutId;//布局id + private boolean isScrolling;//是否在滚动 + private OnItemClickListener listener;//点击事件监听器 + private OnItemLongClickListener longClickListener;//长按监听器 + private RecyclerView recyclerView; + + //在RecyclerView提供数据的时候调用 + @Override + public void onAttachedToRecyclerView(RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + this.recyclerView = recyclerView; + } + + @Override + public void onDetachedFromRecyclerView(RecyclerView recyclerView) { + super.onDetachedFromRecyclerView(recyclerView); + this.recyclerView = null; + } + + /** + * 定义一个点击事件接口回调 + */ + public interface OnItemClickListener { + void onItemClick(RecyclerView parent, View view, int position); + } + + public interface OnItemLongClickListener { + boolean onItemLongClick(RecyclerView parent, View view, int position); + } + + /** + * 插入一项 + * + * @param item + * @param position + */ + public void insert(T item, int position) { + list.add(position, item); + notifyItemInserted(position); + } + + /** + * 删除一项 + * + * @param position 删除位置 + */ + public void delete(int position) { + list.remove(position); + notifyItemRemoved(position); + } + + + public CommonRecyclerAdapter(Context context, List list, int itemLayoutId) { + this.context = context; + this.list = list; + this.itemLayoutId = itemLayoutId; + inflater = LayoutInflater.from(context); + + // recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + // @Override + // public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + // super.onScrollStateChanged(recyclerView, newState); + // isScrolling = !(newState == RecyclerView.SCROLL_STATE_IDLE); + // if (!isScrolling) { + // notifyDataSetChanged(); + // } + // } + // }); + } + + @Override + public CommonRecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = inflater.inflate(itemLayoutId, parent, false); + return CommonRecyclerHolder.getRecyclerHolder(context, view); + } + + @Override + public void onBindViewHolder(final CommonRecyclerHolder holder, int position) { + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null && view != null && recyclerView != null) { + int position = recyclerView.getChildAdapterPosition(view); + listener.onItemClick(recyclerView, view, position); + } + } + }); + + + holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + if (longClickListener != null && view != null && recyclerView != null) { + int position = recyclerView.getChildAdapterPosition(view); + longClickListener.onItemLongClick(recyclerView, view, position); + return true; + } + return false; + } + }); + + convert(holder, list.get(position), position, isScrolling); + + } + + @Override + public int getItemCount() { + return list == null ? 0 : list.size(); + } + + public void setOnItemClickListener(OnItemClickListener listener) { + this.listener = listener; + } + + public void setOnItemLongClickListener(OnItemLongClickListener longClickListener) { + this.longClickListener = longClickListener; + } + + /** + * 填充RecyclerView适配器的方法,子类需要重写 + * + * @param holder ViewHolder + * @param item 子项 + * @param position 位置 + * @param isScrolling 是否在滑动 + */ + public abstract void convert(CommonRecyclerHolder holder, T item, int position, boolean isScrolling); +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/CommonRecyclerHolder.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/CommonRecyclerHolder.java new file mode 100644 index 0000000..e6bde7e --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/CommonRecyclerHolder.java @@ -0,0 +1,143 @@ +package com.example.nanchen.aiyaschoolpush.adapter; + +import android.content.Context; +import android.graphics.Bitmap; +import android.support.v7.widget.RecyclerView; +import android.util.SparseArray; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageView; +import android.widget.TextView; + +import com.example.nanchen.aiyaschoolpush.R; +import com.lzy.ninegrid.NineGridView; +import com.lzy.ninegrid.preview.NineGridViewClickAdapter; +import com.squareup.picasso.Picasso; + +/** + * @author nanchen + * @fileName ischool + * @packageName com.idtechinfo.shouxiner.adapter.common + * @date 2016/09/02 15:41 + */ +public class CommonRecyclerHolder extends RecyclerView.ViewHolder { + private SparseArray views; + private Context context; + + private CommonRecyclerHolder(Context context, View itemView) { + super(itemView); + this.context = context; + //指定一个初始为8 + views = new SparseArray<>(8); + } + + /** + * 取得一个RecyclerHolder对象 + * @param context 上下文 + * @param itemView 子项 + * @return 返回一个RecyclerHolder对象 + */ + public static CommonRecyclerHolder getRecyclerHolder(Context context, View itemView){ + return new CommonRecyclerHolder(context,itemView); + } + + public SparseArray getViews(){ + return this.views; + } + + /** + * 通过view的id获取对应的控件,如果没有则加入views中 + * @param viewId 控件的id + * @return 返回一个控件 + */ + @SuppressWarnings("unchecked") + public T getView(int viewId){ + View view = views.get(viewId); + if (view == null ){ + view = itemView.findViewById(viewId); + views.put(viewId,view); + } + return (T) view; + } + + /** + * 设置字符串 + */ + public CommonRecyclerHolder setText(int viewId, String text){ + TextView tv = getView(viewId); + tv.setText(text); + return this; + } + + public CommonRecyclerHolder setTextColor(int viewId,int color){ + TextView textView = getView(viewId); + textView.setTextColor(color); + return this; + } + + /** + * 设置图片 + */ + public CommonRecyclerHolder setImageResource(int viewId, int drawableId){ + ImageView iv = getView(viewId); + iv.setImageResource(drawableId); + return this; + } + + /** + * 设置图片 + */ + public CommonRecyclerHolder setImageBitmap(int viewId, Bitmap bitmap){ + ImageView iv = getView(viewId); + iv.setImageBitmap(bitmap); + return this; + } + + /** + * 设置图片 + */ + public CommonRecyclerHolder setImageByUrl(int viewId, String url){ + Picasso.with(context).load(url) + .placeholder(context.getResources().getDrawable(R.drawable.ic_default_image)) + .error(context.getResources().getDrawable(R.drawable.ic_default_image)) + .into((ImageView) getView(viewId)); + return this; + } + + + + public CommonRecyclerHolder setOnRecyclerItemClickListener(int viewId,OnClickListener listener){ + View view = getView(viewId); + view.setOnClickListener(listener); + return this; + } + + /** + * 设置九宫格图片 + * @param viewId id + * @param clickAdapter Adapter + */ + public CommonRecyclerHolder setNineGridAdapter(int viewId,NineGridViewClickAdapter clickAdapter){ + NineGridView nineGridView = getView(viewId); + nineGridView.setAdapter(clickAdapter); + return this; + } + + /** + * 设置一个控件是否可见 + * @param viewId id + * @param visibility 可见性 + * @return + */ + public CommonRecyclerHolder setVisibility(int viewId,int visibility){ + View view = getView(viewId); + view.setVisibility(visibility); + return this; + } + + public CommonRecyclerHolder setOnClckListener(int viewId,OnClickListener listener){ + View view = getView(viewId); + view.setOnClickListener(listener); + return this; + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/CropOptionAdapter.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/CropOptionAdapter.java new file mode 100644 index 0000000..d29219e --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/CropOptionAdapter.java @@ -0,0 +1,51 @@ +package com.example.nanchen.aiyaschoolpush.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.example.nanchen.aiyaschoolpush.CropOption; +import com.example.nanchen.aiyaschoolpush.R; + +import java.util.ArrayList; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.adapter + * @date 2016/09/28 15:26 + */ + +public class CropOptionAdapter extends ArrayAdapter { + private ArrayList mOptions; + private LayoutInflater mInflater; + + public CropOptionAdapter(Context context, ArrayList options) { + super(context, R.layout.layout_crop_selector, options); + mOptions = options; + mInflater = LayoutInflater.from(context); + } + + @Override + public View getView(int position, View convertView, ViewGroup group) { + if (convertView == null) + convertView = mInflater.inflate(R.layout.layout_crop_selector, null); + + CropOption item = mOptions.get(position); + + if (item != null) { + ((ImageView) convertView.findViewById(R.id.iv_icon)) + .setImageDrawable(item.icon); + ((TextView) convertView.findViewById(R.id.tv_name)) + .setText(item.title); + + return convertView; + } + + return null; + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/GroupAdapter.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/GroupAdapter.java new file mode 100644 index 0000000..b2f638a --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/GroupAdapter.java @@ -0,0 +1,125 @@ +/** + * Copyright (C) 2016 Hyphenate Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.nanchen.aiyaschoolpush.adapter; + +import android.content.Context; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import com.example.nanchen.aiyaschoolpush.R; +import com.hyphenate.chat.EMGroup; + +import java.util.List; + +public class GroupAdapter extends ArrayAdapter { + + private LayoutInflater inflater; + private String newGroup; + private String addPublicGroup; + + public GroupAdapter(Context context, int res, List groups) { + super(context, res, groups); + this.inflater = LayoutInflater.from(context); + newGroup = context.getResources().getString(R.string.The_new_group_chat); + addPublicGroup = context.getResources().getString(R.string.add_public_group_chat); + } + + @Override + public int getViewTypeCount() { + return 4; + } + + @Override + public int getItemViewType(int position) { + if (position == 0) { + return 0; + } else if (position == 1) { + return 1; + } else if (position == 2) { + return 2; + } else { + return 3; + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (getItemViewType(position) == 0) { + if (convertView == null) { + convertView = inflater.inflate(R.layout.em_search_bar_with_padding, parent, false); + } + final EditText query = (EditText) convertView.findViewById(R.id.query); + final ImageButton clearSearch = (ImageButton) convertView.findViewById(R.id.search_clear); + query.addTextChangedListener(new TextWatcher() { + public void onTextChanged(CharSequence s, int start, int before, int count) { + getFilter().filter(s); + if (s.length() > 0) { + clearSearch.setVisibility(View.VISIBLE); + } else { + clearSearch.setVisibility(View.INVISIBLE); + } + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + public void afterTextChanged(Editable s) { + } + }); + clearSearch.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + query.getText().clear(); + } + }); + } else if (getItemViewType(position) == 1) { + if (convertView == null) { + convertView = inflater.inflate(R.layout.em_row_add_group, parent, false); + } + ((ImageView) convertView.findViewById(R.id.avatar)).setImageResource(R.drawable.em_create_group); + ((TextView) convertView.findViewById(R.id.name)).setText(newGroup); + } else if (getItemViewType(position) == 2) { + if (convertView == null) { + convertView = inflater.inflate(R.layout.em_row_add_group, parent, false); + } + ((ImageView) convertView.findViewById(R.id.avatar)).setImageResource(R.drawable.em_add_public_group); + ((TextView) convertView.findViewById(R.id.name)).setText(addPublicGroup); + ((TextView) convertView.findViewById(R.id.header)).setVisibility(View.VISIBLE); + + } else { + if (convertView == null) { + convertView = inflater.inflate(R.layout.em_row_group, parent, false); + } + ((TextView) convertView.findViewById(R.id.name)).setText(getItem(position - 3).getGroupName()); + + } + + return convertView; + } + + @Override + public int getCount() { + return super.getCount() + 3; + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/GuidePagerAdapter.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/GuidePagerAdapter.java new file mode 100644 index 0000000..37223b6 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/GuidePagerAdapter.java @@ -0,0 +1,48 @@ +package com.example.nanchen.aiyaschoolpush.adapter; + +import android.content.Context; +import android.support.v4.view.PagerAdapter; +import android.view.View; +import android.view.ViewGroup; + +import java.util.List; + +/** + * 引导页Guide的适配器 + * + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.adapter + * @date 2016/09/13 17:15 + */ +public class GuidePagerAdapter extends PagerAdapter { + private Context context; + private List views; + + public GuidePagerAdapter(Context context, List views) { + this.context = context; + this.views = views; + } + + @Override + public int getCount() { + return views == null ? 0 : views.size(); + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + container.removeView(views.get(position)); + } + + + @Override + public Object instantiateItem(ViewGroup container, int position) { + container.addView(views.get(position)); + return views.get(position); + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/ImagePickerAdapter.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/ImagePickerAdapter.java new file mode 100644 index 0000000..f20a642 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/ImagePickerAdapter.java @@ -0,0 +1,112 @@ +package com.example.nanchen.aiyaschoolpush.adapter; + +import android.app.Activity; +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.example.nanchen.aiyaschoolpush.R; +import com.example.nanchen.aiyaschoolpush.ui.activity.ReleaseActivity; +import com.lzy.imagepicker.ImagePicker; +import com.lzy.imagepicker.bean.ImageItem; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush + * @date 2016/11/23 14:21 + */ + +public class ImagePickerAdapter extends RecyclerView.Adapter { + private int maxImgCount; + private Context mContext; + private List mData; + private LayoutInflater mInflater; + private OnRecyclerViewItemClickListener listener; + private boolean isAdded; //是否额外添加了最后一个图片 + + public interface OnRecyclerViewItemClickListener { + void onItemClick(View view, int position); + } + + public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) { + this.listener = listener; + } + + public void setImages(List data) { + mData = new ArrayList<>(data); + if (getItemCount() < maxImgCount) { + mData.add(new ImageItem()); + isAdded = true; + } else { + isAdded = false; + } + notifyDataSetChanged(); + } + + public List getImages() { + //由于图片未选满时,最后一张显示添加图片,因此这个方法返回真正的已选图片 + if (isAdded) return new ArrayList<>(mData.subList(0, mData.size() - 1)); + else return mData; + } + + public ImagePickerAdapter(Context mContext, List data, int maxImgCount) { + this.mContext = mContext; + this.maxImgCount = maxImgCount; + this.mInflater = LayoutInflater.from(mContext); + setImages(data); + } + + @Override + public SelectedPicViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new SelectedPicViewHolder(mInflater.inflate(R.layout.layout_item_image, parent, false)); + } + + @Override + public void onBindViewHolder(SelectedPicViewHolder holder, int position) { + holder.bind(position); + } + + @Override + public int getItemCount() { + return mData.size(); + } + + public class SelectedPicViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + private ImageView iv_img; + private int clickPosition; + + public SelectedPicViewHolder(View itemView) { + super(itemView); + iv_img = (ImageView) itemView.findViewById(R.id.iv_img); + } + + public void bind(int position) { + //设置条目的点击事件 + itemView.setOnClickListener(this); + //根据条目位置设置图片 + ImageItem item = mData.get(position); + if (isAdded && position == getItemCount() - 1) { + iv_img.setImageResource(R.drawable.selector_image_add); + clickPosition = ReleaseActivity.IMAGE_ITEM_ADD; + } else { + ImagePicker.getInstance().getImageLoader().displayImage((Activity) mContext, item.path, iv_img, 0, 0); + clickPosition = position; + } + } + + @Override + public void onClick(View v) { + Log.e("ReleaseActivity","缩略宽:"+iv_img.getWidth()+",缩略高:"+iv_img.getHeight()); + if (listener != null) listener.onItemClick(v, clickPosition); + } + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/MyPagerAdapter.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/MyPagerAdapter.java new file mode 100644 index 0000000..9e9ac74 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/MyPagerAdapter.java @@ -0,0 +1,42 @@ +package com.example.nanchen.aiyaschoolpush.adapter; + +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; + +import java.util.List; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.adapter + * @date 2016/10/08 10:11 + */ + +public class MyPagerAdapter extends FragmentPagerAdapter { + private List list; + + private List mName; + + public MyPagerAdapter(FragmentManager fm,List name,List list) { + super(fm); + this.list = list; + this.mName = name; + + } + + @Override + public Fragment getItem(int position) { + return list.get(position); + } + + @Override + public int getCount() { + return list == null ? 0:list.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + return mName.get(position); + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/NewFriendsMsgAdapter.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/NewFriendsMsgAdapter.java new file mode 100644 index 0000000..7d3bf23 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/NewFriendsMsgAdapter.java @@ -0,0 +1,289 @@ +/** + * Copyright (C) 2016 Hyphenate Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.nanchen.aiyaschoolpush.adapter; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.ContentValues; +import android.content.Context; +import android.text.TextUtils; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.example.nanchen.aiyaschoolpush.R; +import com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao; +import com.example.nanchen.aiyaschoolpush.im.InviteMesageStatus; +import com.example.nanchen.aiyaschoolpush.im.InviteMessage; +import com.hyphenate.chat.EMClient; + +import java.util.List; + +public class NewFriendsMsgAdapter extends ArrayAdapter { + + private Context context; + private InviteMessgeDao messgeDao; + + public NewFriendsMsgAdapter(Context context, int textViewResourceId, List objects) { + super(context, textViewResourceId, objects); + this.context = context; + messgeDao = new InviteMessgeDao(context); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final ViewHolder holder; + if (convertView == null) { + holder = new ViewHolder(); + convertView = View.inflate(context, R.layout.layout_em_row_invite_msg, null); + holder.avator = (ImageView) convertView.findViewById(R.id.avatar); + holder.reason = (TextView) convertView.findViewById(R.id.message); + holder.name = (TextView) convertView.findViewById(R.id.name); + holder.agree = (Button) convertView.findViewById(R.id.agree); + holder.status = (Button) convertView.findViewById(R.id.user_state); + holder.groupContainer = (LinearLayout) convertView.findViewById(R.id.ll_group); + holder.groupname = (TextView) convertView.findViewById(R.id.tv_groupName); + // holder.time = (TextView) convertView.findViewById(R.id.time); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + + String str1 = context.getResources().getString(R.string.Has_agreed_to_your_friend_request); + String str2 = context.getResources().getString(R.string.agree); + + String str3 = context.getResources().getString(R.string.Request_to_add_you_as_a_friend); + String str4 = context.getResources().getString(R.string.Apply_to_the_group_of); + String str5 = context.getResources().getString(R.string.Has_agreed_to); + String str6 = context.getResources().getString(R.string.Has_refused_to); + + String str7 = context.getResources().getString(R.string.refuse); + String str8 = context.getResources().getString(R.string.invite_join_group); + String str9 = context.getResources().getString(R.string.accept_join_group); + String str10 = context.getResources().getString(R.string.refuse_join_group); + + final InviteMessage msg = getItem(position); + if (msg != null) { + + holder.agree.setVisibility(View.INVISIBLE); + + if(msg.getGroupId() != null){ // show group name + holder.groupContainer.setVisibility(View.VISIBLE); + holder.groupname.setText(msg.getGroupName()); + } else{ + holder.groupContainer.setVisibility(View.GONE); + } + + holder.reason.setText(msg.getReason()); + holder.name.setText(msg.getFrom()); + // holder.time.setText(DateUtils.getTimestampString(new + // Date(msg.getTime()))); + if (msg.getStatus() == InviteMesageStatus.BEAGREED) { + holder.status.setVisibility(View.INVISIBLE); + holder.reason.setText(str1); + } else if (msg.getStatus() == InviteMesageStatus.BEINVITEED || msg.getStatus() == InviteMesageStatus.BEAPPLYED || + msg.getStatus() == InviteMesageStatus.GROUPINVITATION) { + holder.agree.setVisibility(View.VISIBLE); + holder.agree.setEnabled(true); + holder.agree.setBackgroundResource(android.R.drawable.btn_default); + holder.agree.setText(str2); + + holder.status.setVisibility(View.VISIBLE); + holder.status.setEnabled(true); + holder.status.setBackgroundResource(android.R.drawable.btn_default); + holder.status.setText(str7); + if(msg.getStatus() == InviteMesageStatus.BEINVITEED){ + if (msg.getReason() == null) { + // use default text + holder.reason.setText(str3); + } + }else if (msg.getStatus() == InviteMesageStatus.BEAPPLYED) { //application to join group + if (TextUtils.isEmpty(msg.getReason())) { + holder.reason.setText(str4 + msg.getGroupName()); + } + } else if (msg.getStatus() == InviteMesageStatus.GROUPINVITATION) { + if (TextUtils.isEmpty(msg.getReason())) { + holder.reason.setText(str8 + msg.getGroupName()); + } + } + + // set click listener + holder.agree.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + // accept invitation + acceptInvitation(holder.agree, holder.status, msg); + } + }); + holder.status.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + // decline invitation + refuseInvitation(holder.agree, holder.status, msg); + } + }); + } else if (msg.getStatus() == InviteMesageStatus.AGREED) { + holder.status.setText(str5); + holder.status.setBackgroundDrawable(null); + holder.status.setEnabled(false); + } else if(msg.getStatus() == InviteMesageStatus.REFUSED){ + holder.status.setText(str6); + holder.status.setBackgroundDrawable(null); + holder.status.setEnabled(false); + } else if(msg.getStatus() == InviteMesageStatus.GROUPINVITATION_ACCEPTED){ + String str = msg.getGroupInviter() + str9 + msg.getGroupName(); + holder.status.setText(str); + holder.status.setBackgroundDrawable(null); + holder.status.setEnabled(false); + } else if(msg.getStatus() == InviteMesageStatus.GROUPINVITATION_DECLINED){ + String str = msg.getGroupInviter() + str10 + msg.getGroupName(); + holder.status.setText(str); + holder.status.setBackgroundDrawable(null); + holder.status.setEnabled(false); + } + } + + return convertView; + } + + /** + * accept invitation + * + */ + private void acceptInvitation(final Button buttonAgree, final Button buttonRefuse, final InviteMessage msg) { + final ProgressDialog pd = new ProgressDialog(context); + String str1 = context.getResources().getString(R.string.Are_agree_with); + final String str2 = context.getResources().getString(R.string.Has_agreed_to); + final String str3 = context.getResources().getString(R.string.Agree_with_failure); + pd.setMessage(str1); + pd.setCanceledOnTouchOutside(false); + pd.show(); + + new Thread(new Runnable() { + public void run() { + // call api + try { + if (msg.getStatus() == InviteMesageStatus.BEINVITEED) {//accept be friends + EMClient.getInstance().contactManager().acceptInvitation(msg.getFrom()); + } else if (msg.getStatus() == InviteMesageStatus.BEAPPLYED) { //accept application to join group + EMClient.getInstance().groupManager().acceptApplication(msg.getFrom(), msg.getGroupId()); + } else if (msg.getStatus() == InviteMesageStatus.GROUPINVITATION) { + EMClient.getInstance().groupManager().acceptInvitation(msg.getGroupId(), msg.getGroupInviter()); + } + msg.setStatus(InviteMesageStatus.AGREED); + // update database + ContentValues values = new ContentValues(); + values.put(InviteMessgeDao.COLUMN_NAME_STATUS, msg.getStatus().ordinal()); + messgeDao.updateMessage(msg.getId(), values); + ((Activity) context).runOnUiThread(new Runnable() { + + @Override + public void run() { + pd.dismiss(); + buttonAgree.setText(str2); + buttonAgree.setBackgroundDrawable(null); + buttonAgree.setEnabled(false); + + buttonRefuse.setVisibility(View.INVISIBLE); + } + }); + } catch (final Exception e) { + ((Activity) context).runOnUiThread(new Runnable() { + + @Override + public void run() { + pd.dismiss(); + Toast.makeText(context, str3 + e.getMessage(), Toast.LENGTH_LONG).show(); + } + }); + + } + } + }).start(); + } + + /** + * decline invitation + * + */ + private void refuseInvitation(final Button buttonAgree, final Button buttonRefuse, final InviteMessage msg) { + final ProgressDialog pd = new ProgressDialog(context); + String str1 = context.getResources().getString(R.string.Are_refuse_with); + final String str2 = context.getResources().getString(R.string.Has_refused_to); + final String str3 = context.getResources().getString(R.string.Refuse_with_failure); + pd.setMessage(str1); + pd.setCanceledOnTouchOutside(false); + pd.show(); + + new Thread(new Runnable() { + public void run() { + // call api + try { + if (msg.getStatus() == InviteMesageStatus.BEINVITEED) {//decline the invitation + EMClient.getInstance().contactManager().declineInvitation(msg.getFrom()); + } else if (msg.getStatus() == InviteMesageStatus.BEAPPLYED) { //decline application to join group + EMClient.getInstance().groupManager().declineApplication(msg.getFrom(), msg.getGroupId(), ""); + } else if (msg.getStatus() == InviteMesageStatus.GROUPINVITATION) { + EMClient.getInstance().groupManager().declineInvitation(msg.getGroupId(), msg.getGroupInviter(), ""); + } + msg.setStatus(InviteMesageStatus.REFUSED); + // update database + ContentValues values = new ContentValues(); + values.put(InviteMessgeDao.COLUMN_NAME_STATUS, msg.getStatus().ordinal()); + messgeDao.updateMessage(msg.getId(), values); + ((Activity) context).runOnUiThread(new Runnable() { + + @Override + public void run() { + pd.dismiss(); + buttonRefuse.setText(str2); + buttonRefuse.setBackgroundDrawable(null); + buttonRefuse.setEnabled(false); + + buttonAgree.setVisibility(View.INVISIBLE); + } + }); + } catch (final Exception e) { + ((Activity) context).runOnUiThread(new Runnable() { + + @Override + public void run() { + pd.dismiss(); + Toast.makeText(context, str3 + e.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + + } + } + }).start(); + } + + private static class ViewHolder { + ImageView avator; + TextView name; + TextView reason; + Button agree; + Button status; + LinearLayout groupContainer; + TextView groupname; + // TextView time; + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/ViewHolder.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/ViewHolder.java new file mode 100644 index 0000000..db1ebdf --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/adapter/ViewHolder.java @@ -0,0 +1,122 @@ +package com.example.nanchen.aiyaschoolpush.adapter; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.squareup.picasso.Picasso; + +/** + * 万能适配器的ViewHolder + * + * @author nanchen + * @date 2016-07-29 15:04:10 + */ +public class ViewHolder { + + //现在对于int作为键的官方推荐用SparseArray替代HashMap + private final SparseArray views; + private View convertView; + private Context context; + + private ViewHolder(Context context,ViewGroup parent,int itemLayoutId,int position) { + this.context = context; + this.views = new SparseArray<>(); + this.convertView = LayoutInflater.from(context).inflate(itemLayoutId,parent,false); + convertView.setTag(this); + } + + /** + * 拿到一个ViewHolder对象 + */ + public static ViewHolder get(Context context,View convertView, ViewGroup parent, int layoutId, int position) { + if (convertView == null) { + return new ViewHolder(context,parent, layoutId, position); + } + return (ViewHolder) convertView.getTag(); + } + + /** + * 通过控件的Id获取对于的控件,如果没有则加入views + */ + public T getView(int viewId) { + View view = views.get(viewId); + if (view == null) { + view = convertView.findViewById(viewId); + views.put(viewId, view); + } + return (T) view; + } + + public View getConvertView() { + return convertView; + } + + /** + * 设置字符串 + */ + public ViewHolder setText(int viewId,String text){ + TextView tv = getView(viewId); + tv.setText(text); + return this; + } + + public ViewHolder setText(int viewId,CharSequence text){ + TextView tv = getView(viewId); + tv.setText(text); + return this; + } + + + /** + * 设置图片 + */ + public ViewHolder setImageResource(int viewId,int drawableId){ + ImageView iv = getView(viewId); + iv.setImageResource(drawableId); + return this; + } + + /** + * 设置图片 + */ + public ViewHolder setImageDrawable(int viewId, Drawable drawable){ + ImageView iv = getView(viewId); + iv.setImageDrawable(drawable); + return this; + } + + /** + * 设置图片 + */ + public ViewHolder setImageBitmap(int viewId, Bitmap bitmap){ + ImageView iv = getView(viewId); + iv.setImageBitmap(bitmap); + return this; + } + + /** + * 设置图片 + */ + public ViewHolder setImageByUrl(int viewId,String url){ + Picasso.with(context).load(url).into((ImageView) getView(viewId)); + // ImageLoader.getInstance().init(ImageLoaderConfiguration.createDefault(context)); + // ImageLoader.getInstance().displayImage(url, (ImageView) getView(viewId)); + return this; + } + + /** + * 设置文本颜色 + */ + public ViewHolder setTextColor(int viewId,int color){ + TextView textView = getView(viewId); + textView.setTextColor(color); + return this; + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/behavior/FloatingActionButtonScrollBehavior.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/behavior/FloatingActionButtonScrollBehavior.java new file mode 100644 index 0000000..dbf534a --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/behavior/FloatingActionButtonScrollBehavior.java @@ -0,0 +1,80 @@ +package com.example.nanchen.aiyaschoolpush.behavior; + +import android.content.Context; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.CoordinatorLayout.Behavior; +import android.support.design.widget.CoordinatorLayout.LayoutParams; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.behavior + * @date 2016/10/13 09:24 + */ + +public class FloatingActionButtonScrollBehavior extends FloatingActionButton.Behavior { + + public FloatingActionButtonScrollBehavior(Context context, AttributeSet attrs) { + super(); + } + + @Override + public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final + FloatingActionButton child, final View directTargetChild, final View target, final int + nestedScrollAxes) { + // 确保是竖直判断的滚动 + return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll + (coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); + } + + @Override + public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final + FloatingActionButton child, final View target, final int dxConsumed, final int dyConsumed, + final int dxUnconsumed, final int dyUnconsumed) { + super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, + dxUnconsumed, dyUnconsumed); + if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) { // 往下滑 + child.hide(); + if (mOnStateChangedListener != null){ + mOnStateChangedListener.onChanged(false); + } + } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) { + child.show(); + if (mOnStateChangedListener != null){ + mOnStateChangedListener.onChanged(true); + } + } + } + + /** + * 定义一个接口用于隐藏导航栏 + */ + public interface OnStateChangedListener{ + void onChanged(boolean isShow); + } + + private OnStateChangedListener mOnStateChangedListener; + + public void setOnStateChangedListener(OnStateChangedListener onStateChangedListener) { + mOnStateChangedListener = onStateChangedListener; + } + + public static FloatingActionButtonScrollBehavior from(V view){ + ViewGroup.LayoutParams params = view.getLayoutParams(); + if (!(params instanceof CoordinatorLayout.LayoutParams)){ + throw new IllegalArgumentException("这个View不是CoodinatorLayout的子View"); + } + Behavior behavior = ((LayoutParams)params).getBehavior(); + if (!(behavior instanceof FloatingActionButtonScrollBehavior)){ + throw new IllegalArgumentException("这个View的Behavior不是FloatingActionButtonScrollBehavior"); + } + return (FloatingActionButtonScrollBehavior) behavior; + } +} + + diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/behavior/MyBehavior.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/behavior/MyBehavior.java new file mode 100644 index 0000000..ad9c9e4 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/behavior/MyBehavior.java @@ -0,0 +1,70 @@ +package com.example.nanchen.aiyaschoolpush.behavior; + +import android.content.Context; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.CoordinatorLayout.Behavior; +import android.support.design.widget.CoordinatorLayout.LayoutParams; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.behavior + * @date 2016/10/13 09:23 + */ + +public class MyBehavior extends CoordinatorLayout.Behavior { + //写了这个构造方法才能在XML文件中直接指定 + public MyBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { + return true;//返回true代表我们关心这个滚动事件 + } + + @Override + public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) { + super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); + if (dy < 0) {//向下滚动 + ViewCompat.animate(child).scaleX(1).alpha(1).start(); + if (mOnStateChangedListener != null){ + mOnStateChangedListener.onChanged(true); + } + } else {//向上滚动 + ViewCompat.animate(child).scaleX(0).alpha(0).start(); + if (mOnStateChangedListener != null){ + mOnStateChangedListener.onChanged(false); + } + } + } + + /** + * 定义一个接口用于隐藏导航栏 + */ + public interface OnStateChangedListener{ + void onChanged(boolean isShow); + } + + private OnStateChangedListener mOnStateChangedListener; + + public void setOnStateChangedListener(OnStateChangedListener onStateChangedListener) { + mOnStateChangedListener = onStateChangedListener; + } + + public static MyBehavior from(V view){ + ViewGroup.LayoutParams params = view.getLayoutParams(); + if (!(params instanceof CoordinatorLayout.LayoutParams)){ + throw new IllegalArgumentException("这个View不是CoodinatorLayout的子View"); + } + Behavior behavior = ((LayoutParams)params).getBehavior(); + if (!(behavior instanceof MyBehavior)){ + throw new IllegalArgumentException("这个View的Behavior不是MyBehavior"); + } + return (MyBehavior) behavior; + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/behavior/ScrollAwareFABBehavior.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/behavior/ScrollAwareFABBehavior.java new file mode 100644 index 0000000..03ef847 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/behavior/ScrollAwareFABBehavior.java @@ -0,0 +1,85 @@ +package com.example.nanchen.aiyaschoolpush.behavior; + +import android.content.Context; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPropertyAnimatorListener; +import android.support.v4.view.animation.FastOutSlowInInterpolator; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Interpolator; + +/** + * Author: nanchen + * Email: liushilin520@foxmail.com + * Date: 2017-04-28 11:41 + */ + +public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior { + private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator(); + private boolean mIsAnimatingOut = false; + + public ScrollAwareFABBehavior(Context context, AttributeSet attrs) { + super(); + } + + @Override + public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child, + final View directTargetChild, final View target, final int nestedScrollAxes) { + // Ensure we react to vertical scrolling + return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL + || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); + } + + @Override + public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child, + final View target, final int dxConsumed, final int dyConsumed, + final int dxUnconsumed, final int dyUnconsumed) { + super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); + if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) { + // User scrolled down and the FAB is currently visible -> hide the FAB + animateOut(child); + } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) { + // User scrolled up and the FAB is currently not visible -> show the FAB + animateIn(child); + } + } + + // Same animation that FloatingActionButton.Behavior uses to hide the FAB when the AppBarLayout exits + private void animateOut(final FloatingActionButton button) { + ViewCompat.animate(button).translationY(button.getHeight() + getMarginBottom(button)).setInterpolator(INTERPOLATOR).withLayer() + .setListener(new ViewPropertyAnimatorListener() { + public void onAnimationStart(View view) { + ScrollAwareFABBehavior.this.mIsAnimatingOut = true; + } + + public void onAnimationCancel(View view) { + ScrollAwareFABBehavior.this.mIsAnimatingOut = false; + } + + public void onAnimationEnd(View view) { + ScrollAwareFABBehavior.this.mIsAnimatingOut = false; + view.setVisibility(View.GONE); + } + }).start(); + } + + // Same animation that FloatingActionButton.Behavior uses to show the FAB when the AppBarLayout enters + private void animateIn(FloatingActionButton button) { + button.setVisibility(View.VISIBLE); + ViewCompat.animate(button).translationY(0) + .setInterpolator(INTERPOLATOR).withLayer().setListener(null) + .start(); + } + + private int getMarginBottom(View v) { + int marginBottom = 0; + final ViewGroup.LayoutParams layoutParams = v.getLayoutParams(); + if (layoutParams instanceof ViewGroup.MarginLayoutParams) { + marginBottom = ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin; + } + return marginBottom; + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/behavior/SimpleViewBehavior.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/behavior/SimpleViewBehavior.java new file mode 100644 index 0000000..ede31e5 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/behavior/SimpleViewBehavior.java @@ -0,0 +1,282 @@ +package com.example.nanchen.aiyaschoolpush.behavior; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.ColorDrawable; +import android.os.Build; +import android.support.design.widget.AppBarLayout; +import android.support.design.widget.CoordinatorLayout; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.Transformation; + +import com.example.nanchen.aiyaschoolpush.R; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.behavior + * @date 2016/12/20 14:00 + */ + +public class SimpleViewBehavior extends CoordinatorLayout.Behavior { + + private static final int UNSPECIFIED_INT = Integer.MAX_VALUE; + private static final float UNSPECIFIED_FLOAT = Float.MAX_VALUE; + + private static final int DEPEND_TYPE_HEIGHT = 0; + private static final int DEPEND_TYPE_WIDTH = 1; + private static final int DEPEND_TYPE_X = 2; + private static final int DEPEND_TYPE_Y = 3; + + private int mDependViewId = 0; //默认没有依赖对象 + private int mDependType = DEPEND_TYPE_Y; //默认按照y方向变化 + private int mDependTargetX; //X方向的允许最大距离(影响动画percent) + private int mDependTargetY; //Y方向的允许最大距离(影响动画percent) + private int mDependTargetWidth; //依赖控件起始最大宽度(影响动画percent) + private int mDependTargetHeight; //依赖控件起始最大高度(影响动画percent) + private int targetX; + private int targetY; + private int targetWidth; + private int targetHeight; + private int targetBackgroundColor; + private float targetAlpha; + private float targetRotateX; + private float targetRotateY; + private int mAnimationId = 0; //自定义动画id(xml文件定义动画) + + private int mDependStartX; + private int mDependStartY; + private int mDependStartWidth; + private int mDependStartHeight; + private int mStartX; + private int mStartY; + private int mStartWidth; + private int mStartHeight; + private int mStartBackgroundColor; + private float mStartAlpha; + private float mStartRotateX; + private float mStartRotateY; + + private Animation mAnimation; + private boolean isPrepared; + + public SimpleViewBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SimpleViewBehavior); + mDependViewId = a.getResourceId(R.styleable.SimpleViewBehavior_svb_dependOn, mDependViewId); + mDependType = a.getInt(R.styleable.SimpleViewBehavior_svb_dependType, mDependType); + mDependTargetX = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_dependTargetX, UNSPECIFIED_INT); + mDependTargetY = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_dependTargetY, UNSPECIFIED_INT); + mDependTargetWidth = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_dependTargetWidth, UNSPECIFIED_INT); + mDependTargetHeight = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_dependTargetHeight, UNSPECIFIED_INT); + targetX = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_targetX, UNSPECIFIED_INT); + targetY = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_targetY, UNSPECIFIED_INT); + targetWidth = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_targetWidth, UNSPECIFIED_INT); + targetHeight = a.getDimensionPixelOffset(R.styleable.SimpleViewBehavior_svb_targetHeight, UNSPECIFIED_INT); + targetBackgroundColor = a.getColor(R.styleable.SimpleViewBehavior_svb_targetBackgroundColor, UNSPECIFIED_INT); + targetAlpha = a.getFloat(R.styleable.SimpleViewBehavior_svb_targetAlpha, UNSPECIFIED_FLOAT); + targetRotateX = a.getFloat(R.styleable.SimpleViewBehavior_svb_targetRotateX, UNSPECIFIED_FLOAT); + targetRotateY = a.getFloat(R.styleable.SimpleViewBehavior_svb_targetRotateY, UNSPECIFIED_FLOAT); + mAnimationId = a.getResourceId(R.styleable.SimpleViewBehavior_svb_animation, mAnimationId); + a.recycle(); + } + + /** 初始化数据 */ + private void prepare(CoordinatorLayout parent, View child, View dependency) { + mDependStartX = (int) dependency.getX(); + mDependStartY = (int) dependency.getY(); + mDependStartWidth = dependency.getWidth(); + mDependStartHeight = dependency.getHeight(); + mStartX = (int) child.getX(); + mStartY = (int) child.getY(); + mStartWidth = child.getWidth(); + mStartHeight = child.getHeight(); + mStartAlpha = child.getAlpha(); + mStartRotateX = child.getRotationX(); + mStartRotateY = child.getRotationY(); + + //特殊处理y方向变化 + if (mDependTargetY == UNSPECIFIED_INT && dependency instanceof AppBarLayout) { + mDependTargetY = ((AppBarLayout) dependency).getTotalScrollRange(); + } + // 背景颜色渐变 + if (child.getBackground() instanceof ColorDrawable) mStartBackgroundColor = ((ColorDrawable) child.getBackground()).getColor(); + // 自定义动画 + if (mAnimationId != 0) { + mAnimation = AnimationUtils.loadAnimation(child.getContext(), mAnimationId); + mAnimation.initialize(child.getWidth(), child.getHeight(), parent.getWidth(), parent.getHeight()); + } + // 兼容5.0以上的沉浸模式 + if (Build.VERSION.SDK_INT > 16 && parent.getFitsSystemWindows() && targetY != UNSPECIFIED_INT) { + targetY += getStatusBarHeight(parent.getContext()); + } + isPrepared = true; + } + + /** + * child 是指应用behavior的View ,dependency 担任触发behavior的角色,并与child进行互动。 + * layoutDependsOn方法在每次layout发生变化时都会调用,我们需要在dependency控件发生变化时返回True, + * 在我们的例子中是用户在屏幕上滑动时(因为AppBarLayout发生了移动),然后我们需要让child做出相应的反应。 + */ + @Override + public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { + return dependency.getId() == mDependViewId; + } + + @Override + public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { + // 该方法会在滑动的时候一直回调,但只需要初始化一次 + if (!isPrepared) prepare(parent, child, dependency); + updateView(child, dependency); + return false; + } + + /** + * 这个是CoordinatorLayout在进行measure的过程中,利用Behavior对象对子view进行大小测量的一个方法。 + * 在这个方法内,我们可以通过parent.getDependencies(child);这个方法,获取到这个child依赖的view,然后通过获取这个child依赖的view的大小来决定自身的大小。 + */ + @Override + public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { + return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed); + } + + /** + * 这个方法是用来子view用来布局自身使用,如果依赖其他view,那么系统会首先调用 + * public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) + * 这个方法,可以在这个回调中记录dependency的一些位置信息,在onLayoutChild中利用保存下来的信息进行计算,然后得到自身的具体位置。 + */ + @Override + public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) { + boolean bool = super.onLayoutChild(parent, child, layoutDirection); + if (isPrepared) updateView(child, parent.getDependencies(child).get(0)); + return bool; + } + + public void updateView(View child, View dependency) { + float percent = 0; + float start = 0; + float current = 0; + float end = UNSPECIFIED_INT; + switch (mDependType) { + case DEPEND_TYPE_WIDTH: + start = mDependStartWidth; + current = dependency.getWidth(); + end = mDependTargetWidth; + break; + case DEPEND_TYPE_HEIGHT: + start = mDependStartHeight; + current = dependency.getHeight(); + end = mDependTargetHeight; + break; + case DEPEND_TYPE_X: + start = mDependStartX; + current = dependency.getX(); + end = mDependTargetX; + break; + case DEPEND_TYPE_Y: + start = mDependStartY; + current = dependency.getY(); + end = mDependTargetY; + break; + } + if (end != UNSPECIFIED_INT) { + percent = Math.abs(current - start) / Math.abs(end - start); + } + updateViewWithPercent(child, percent > 1 ? 1 : percent); + } + + /** 更新View */ + @SuppressWarnings("ResourceType") + public void updateViewWithPercent(View child, float percent) { + if (mAnimation == null) { + //如果没有自定义动画,那么使用属性动画 + float newX = targetX == UNSPECIFIED_INT ? 0 : (targetX - mStartX) * percent; + float newY = targetY == UNSPECIFIED_INT ? 0 : (targetY - mStartY) * percent; + //缩放动画 + if (targetWidth != UNSPECIFIED_INT || targetHeight != UNSPECIFIED_INT) { + child.setScaleX(scaleEvaluator(mStartWidth, targetWidth, percent)); + child.setScaleY(scaleEvaluator(mStartHeight, targetHeight, percent)); + float newWidth = floatEvaluator(mStartWidth, targetWidth, percent); + float newHeight = floatEvaluator(mStartWidth, targetWidth, percent); + newX -= (mStartWidth - newWidth) / 2; + newY -= (mStartHeight - newHeight) / 2; + } + //平移动画 + child.setTranslationX(newX); + child.setTranslationY(newY); + //透明度变化 + if (targetAlpha != UNSPECIFIED_FLOAT) child.setAlpha(floatEvaluator(mStartAlpha, targetAlpha, percent)); + //背景渐变 + if (targetBackgroundColor != UNSPECIFIED_INT && mStartBackgroundColor != 0) { + child.setBackgroundColor(argbEvaluator(mStartBackgroundColor, targetBackgroundColor, percent)); + } + //旋转动画 + if (targetRotateX != UNSPECIFIED_FLOAT) child.setRotationX(floatEvaluator(mStartRotateX, targetRotateX, percent)); + if (targetRotateY != UNSPECIFIED_FLOAT) child.setRotationY(floatEvaluator(mStartRotateY, targetRotateY, percent)); + } else { + mAnimation.setStartTime(0); + mAnimation.restrictDuration(100); + Transformation transformation = new Transformation(); + mAnimation.getTransformation((long) (percent * 100), transformation); + BehaviorAnimation animation = new BehaviorAnimation(transformation); + child.startAnimation(animation); + } + child.requestLayout(); + } + + private static class BehaviorAnimation extends Animation { + + private Transformation mTransformation; + + public BehaviorAnimation(Transformation transformation) { + mTransformation = transformation; + setDuration(0); + setFillAfter(true); + } + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + t.compose(mTransformation); + super.applyTransformation(interpolatedTime, t); + } + } + + public static float floatEvaluator(float originalSize, float finalSize, float percent) { + return (finalSize - originalSize) * percent + originalSize; + } + + public static float scaleEvaluator(float originalSize, float finalSize, float percent) { + float calcSize = (finalSize - originalSize) * percent + originalSize; + return calcSize / originalSize; + } + + public static int argbEvaluator(int startColor, int endColor, float percent) { + int startA = (startColor >> 24) & 0xff; + int startR = (startColor >> 16) & 0xff; + int startG = (startColor >> 8) & 0xff; + int startB = startColor & 0xff; + + int endA = (endColor >> 24) & 0xff; + int endR = (endColor >> 16) & 0xff; + int endG = (endColor >> 8) & 0xff; + int endB = endColor & 0xff; + + return ((startA + (int) (percent * (endA - startA))) << 24) | + ((startR + (int) (percent * (endR - startR))) << 16) | + ((startG + (int) (percent * (endG - startG))) << 8) | + ((startB + (int) (percent * (endB - startB)))); + } + + /** 获取状态栏的高度 */ + private static int getStatusBarHeight(Context context) { + int result = 0; + int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = context.getResources().getDimensionPixelSize(resourceId); + } + return result; + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/config/AddConfig.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/config/AddConfig.java new file mode 100644 index 0000000..d0fa33e --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/config/AddConfig.java @@ -0,0 +1,14 @@ +package com.example.nanchen.aiyaschoolpush.config; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.config + * @date 2016/10/13 10:34 + */ + +public class AddConfig { + public static final String NOTICE = "notice"; // 公告 + public static final String HOMEWORK = "homework"; // 作业 + public static final String COMMUNITY = "community"; // 社区 +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/config/Consts.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/config/Consts.java new file mode 100644 index 0000000..accff56 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/config/Consts.java @@ -0,0 +1,31 @@ +package com.example.nanchen.aiyaschoolpush.config; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.config + * @date 2016/11/09 11:34 + * + * 系统常量定义类,此类仅放常量定义,不放任何逻辑代码 + * + */ + +public final class Consts { + /** + * 内网API接口主机名 + */ +// public final static String API_SERVICE_HOST = "http://10.1.1.119:80/AiYaSchoolPush"; + /** + * 外网API接口主机名 + */ + public final static String API_SERVICE_HOST = "http://azhinj.ticp.io:10277/AiYaSchoolPush"; + + /** + * 用户是教师 + */ + public final static int USER_TYPE_TEACHER = 2; + /** + * 用户是学生或家长 + */ + public final static int USER_TYPE_STUDENT = 1; +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/config/WeatherConfig.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/config/WeatherConfig.java new file mode 100644 index 0000000..66a0f55 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/config/WeatherConfig.java @@ -0,0 +1,12 @@ +package com.example.nanchen.aiyaschoolpush.config; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.config + * @date 2016/10/24 11:42 + */ + +public class WeatherConfig { + public static final String APP_KEY = "6601d195ad4e4e5eb21688df3d762fa7"; +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/db/DbOpenHelper.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/db/DbOpenHelper.java new file mode 100644 index 0000000..fa91cb9 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/db/DbOpenHelper.java @@ -0,0 +1,117 @@ +/** + * Copyright (C) 2016 Hyphenate Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.nanchen.aiyaschoolpush.db; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import com.example.nanchen.aiyaschoolpush.helper.DemoHelper; + + +public class DbOpenHelper extends SQLiteOpenHelper{ + + private static final int DATABASE_VERSION = 6; + private static DbOpenHelper instance; + + private static final String USERNAME_TABLE_CREATE = "CREATE TABLE " + + UserDao.TABLE_NAME + " (" + + UserDao.COLUMN_NAME_NICK + " TEXT, " + + UserDao.COLUMN_NAME_AVATAR + " TEXT, " + + UserDao.COLUMN_NAME_ID + " TEXT PRIMARY KEY);"; + + private static final String INIVTE_MESSAGE_TABLE_CREATE = "CREATE TABLE " + + InviteMessgeDao.TABLE_NAME + " (" + + InviteMessgeDao.COLUMN_NAME_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + InviteMessgeDao.COLUMN_NAME_FROM + " TEXT, " + + InviteMessgeDao.COLUMN_NAME_GROUP_ID + " TEXT, " + + InviteMessgeDao.COLUMN_NAME_GROUP_Name + " TEXT, " + + InviteMessgeDao.COLUMN_NAME_REASON + " TEXT, " + + InviteMessgeDao.COLUMN_NAME_STATUS + " INTEGER, " + + InviteMessgeDao.COLUMN_NAME_ISINVITEFROMME + " INTEGER, " + + InviteMessgeDao.COLUMN_NAME_UNREAD_MSG_COUNT + " INTEGER, " + + InviteMessgeDao.COLUMN_NAME_TIME + " TEXT, " + + InviteMessgeDao.COLUMN_NAME_GROUPINVITER + " TEXT); "; + + private static final String ROBOT_TABLE_CREATE = "CREATE TABLE " + + UserDao.ROBOT_TABLE_NAME + " (" + + UserDao.ROBOT_COLUMN_NAME_ID + " TEXT PRIMARY KEY, " + + UserDao.ROBOT_COLUMN_NAME_NICK + " TEXT, " + + UserDao.ROBOT_COLUMN_NAME_AVATAR + " TEXT);"; + + private static final String CREATE_PREF_TABLE = "CREATE TABLE " + + UserDao.PREF_TABLE_NAME + " (" + + UserDao.COLUMN_NAME_DISABLED_GROUPS + " TEXT, " + + UserDao.COLUMN_NAME_DISABLED_IDS + " TEXT);"; + + private DbOpenHelper(Context context) { + super(context, getUserDatabaseName(), null, DATABASE_VERSION); + } + + public static DbOpenHelper getInstance(Context context) { + if (instance == null) { + instance = new DbOpenHelper(context.getApplicationContext()); + } + return instance; + } + + private static String getUserDatabaseName() { + return DemoHelper.getInstance().getCurrentUserName() + "_demo.db"; + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(USERNAME_TABLE_CREATE); + db.execSQL(INIVTE_MESSAGE_TABLE_CREATE); + db.execSQL(CREATE_PREF_TABLE); + db.execSQL(ROBOT_TABLE_CREATE); + + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if(oldVersion < 2){ + db.execSQL("ALTER TABLE "+ UserDao.TABLE_NAME +" ADD COLUMN "+ + UserDao.COLUMN_NAME_AVATAR + " TEXT ;"); + } + + if(oldVersion < 3){ + db.execSQL(CREATE_PREF_TABLE); + } + if(oldVersion < 4){ + db.execSQL(ROBOT_TABLE_CREATE); + } + if(oldVersion < 5){ + db.execSQL("ALTER TABLE " + InviteMessgeDao.TABLE_NAME + " ADD COLUMN " + + InviteMessgeDao.COLUMN_NAME_UNREAD_MSG_COUNT + " INTEGER ;"); + } + if (oldVersion < 6) { + db.execSQL("ALTER TABLE " + InviteMessgeDao.TABLE_NAME + " ADD COLUMN " + + InviteMessgeDao.COLUMN_NAME_GROUPINVITER + " TEXT;"); + } + } + + public void closeDB() { + if (instance != null) { + try { + SQLiteDatabase db = instance.getWritableDatabase(); + db.close(); + } catch (Exception e) { + e.printStackTrace(); + } + instance = null; + } + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/db/DemoDBManager.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/db/DemoDBManager.java new file mode 100644 index 0000000..dcdb81f --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/db/DemoDBManager.java @@ -0,0 +1,309 @@ +package com.example.nanchen.aiyaschoolpush.db; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import com.example.nanchen.aiyaschoolpush.im.Constant; +import com.example.nanchen.aiyaschoolpush.im.InviteMesageStatus; +import com.example.nanchen.aiyaschoolpush.im.InviteMessage; +import com.hyphenate.easeui.domain.EaseUser; +import com.hyphenate.easeui.utils.EaseCommonUtils; +import com.example.nanchen.aiyaschoolpush.App; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +public class DemoDBManager { + static private DemoDBManager dbMgr = new DemoDBManager(); + private com.example.nanchen.aiyaschoolpush.db.DbOpenHelper dbHelper; + + private DemoDBManager(){ + dbHelper = com.example.nanchen.aiyaschoolpush.db.DbOpenHelper.getInstance(App.getInstance().getApplicationContext()); + } + + public static synchronized DemoDBManager getInstance(){ + if(dbMgr == null){ + dbMgr = new DemoDBManager(); + } + return dbMgr; + } + + /** + * save contact list + * + * @param contactList + */ + synchronized public void saveContactList(List contactList) { + SQLiteDatabase db = dbHelper.getWritableDatabase(); + if (db.isOpen()) { + db.delete(com.example.nanchen.aiyaschoolpush.db.UserDao.TABLE_NAME, null, null); + for (EaseUser user : contactList) { + ContentValues values = new ContentValues(); + values.put(com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_ID, user.getUsername()); + if(user.getNick() != null) + values.put(com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_NICK, user.getNick()); + if(user.getAvatar() != null) + values.put(com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_AVATAR, user.getAvatar()); + db.replace(com.example.nanchen.aiyaschoolpush.db.UserDao.TABLE_NAME, null, values); + } + } + } + + /** + * get contact list + * + * @return + */ + synchronized public Map getContactList() { + SQLiteDatabase db = dbHelper.getReadableDatabase(); + Map users = new Hashtable(); + if (db.isOpen()) { + Cursor cursor = db.rawQuery("select * from " + com.example.nanchen.aiyaschoolpush.db.UserDao.TABLE_NAME /* + " desc" */, null); + while (cursor.moveToNext()) { + String username = cursor.getString(cursor.getColumnIndex(com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_ID)); + String nick = cursor.getString(cursor.getColumnIndex(com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_NICK)); + String avatar = cursor.getString(cursor.getColumnIndex(com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_AVATAR)); + EaseUser user = new EaseUser(username); + user.setNick(nick); + user.setAvatar(avatar); + if (username.equals(Constant.NEW_FRIENDS_USERNAME) || username.equals(Constant.GROUP_USERNAME) + || username.equals(Constant.CHAT_ROOM)|| username.equals(Constant.CHAT_ROBOT)) { + user.setInitialLetter(""); + } else { + EaseCommonUtils.setUserInitialLetter(user); + } + users.put(username, user); + } + cursor.close(); + } + return users; + } + + /** + * delete a contact + * @param username + */ + synchronized public void deleteContact(String username){ + SQLiteDatabase db = dbHelper.getWritableDatabase(); + if(db.isOpen()){ + db.delete(com.example.nanchen.aiyaschoolpush.db.UserDao.TABLE_NAME, com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_ID + " = ?", new String[]{username}); + } + } + + /** + * save a contact + * @param user + */ + synchronized public void saveContact(EaseUser user){ + SQLiteDatabase db = dbHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_ID, user.getUsername()); + if(user.getNick() != null) + values.put(com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_NICK, user.getNick()); + if(user.getAvatar() != null) + values.put(com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_AVATAR, user.getAvatar()); + if(db.isOpen()){ + db.replace(com.example.nanchen.aiyaschoolpush.db.UserDao.TABLE_NAME, null, values); + } + } + + public void setDisabledGroups(List groups){ + setList(com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_DISABLED_GROUPS, groups); + } + + public List getDisabledGroups(){ + return getList(com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_DISABLED_GROUPS); + } + + public void setDisabledIds(List ids){ + setList(com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_DISABLED_IDS, ids); + } + + public List getDisabledIds(){ + return getList(com.example.nanchen.aiyaschoolpush.db.UserDao.COLUMN_NAME_DISABLED_IDS); + } + + synchronized private void setList(String column, List strList){ + StringBuilder strBuilder = new StringBuilder(); + + for(String hxid:strList){ + strBuilder.append(hxid).append("$"); + } + + SQLiteDatabase db = dbHelper.getWritableDatabase(); + if (db.isOpen()) { + ContentValues values = new ContentValues(); + values.put(column, strBuilder.toString()); + + db.update(com.example.nanchen.aiyaschoolpush.db.UserDao.PREF_TABLE_NAME, values, null,null); + } + } + + synchronized private List getList(String column){ + SQLiteDatabase db = dbHelper.getReadableDatabase(); + Cursor cursor = db.rawQuery("select " + column + " from " + com.example.nanchen.aiyaschoolpush.db.UserDao.PREF_TABLE_NAME,null); + if (!cursor.moveToFirst()) { + cursor.close(); + return null; + } + + String strVal = cursor.getString(0); + if (strVal == null || strVal.equals("")) { + return null; + } + + cursor.close(); + + String[] array = strVal.split("$"); + + if(array.length > 0){ + List list = new ArrayList(); + Collections.addAll(list, array); + return list; + } + + return null; + } + + /** + * save a message + * @param message + * @return return cursor of the message + */ + public synchronized Integer saveMessage(InviteMessage message){ + SQLiteDatabase db = dbHelper.getWritableDatabase(); + int id = -1; + if(db.isOpen()){ + ContentValues values = new ContentValues(); + values.put(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_FROM, message.getFrom()); + values.put(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_GROUP_ID, message.getGroupId()); + values.put(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_GROUP_Name, message.getGroupName()); + values.put(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_REASON, message.getReason()); + values.put(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_TIME, message.getTime()); + values.put(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_STATUS, message.getStatus().ordinal()); + values.put(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_GROUPINVITER, message.getGroupInviter()); + db.insert(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.TABLE_NAME, null, values); + + Cursor cursor = db.rawQuery("select last_insert_rowid() from " + com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.TABLE_NAME,null); + if(cursor.moveToFirst()){ + id = cursor.getInt(0); + } + + cursor.close(); + } + return id; + } + + /** + * update message + * @param msgId + * @param values + */ + synchronized public void updateMessage(int msgId,ContentValues values){ + SQLiteDatabase db = dbHelper.getWritableDatabase(); + if(db.isOpen()){ + db.update(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.TABLE_NAME, values, com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_ID + " = ?", new String[]{String.valueOf(msgId)}); + } + } + + /** + * get messges + * @return + */ + synchronized public List getMessagesList(){ + SQLiteDatabase db = dbHelper.getReadableDatabase(); + List msgs = new ArrayList(); + if(db.isOpen()){ + Cursor cursor = db.rawQuery("select * from " + com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.TABLE_NAME + " desc",null); + while(cursor.moveToNext()){ + InviteMessage msg = new InviteMessage(); + int id = cursor.getInt(cursor.getColumnIndex(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_ID)); + String from = cursor.getString(cursor.getColumnIndex(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_FROM)); + String groupid = cursor.getString(cursor.getColumnIndex(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_GROUP_ID)); + String groupname = cursor.getString(cursor.getColumnIndex(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_GROUP_Name)); + String reason = cursor.getString(cursor.getColumnIndex(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_REASON)); + long time = cursor.getLong(cursor.getColumnIndex(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_TIME)); + int status = cursor.getInt(cursor.getColumnIndex(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_STATUS)); + String groupInviter = cursor.getString(cursor.getColumnIndex(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_GROUPINVITER)); + + msg.setId(id); + msg.setFrom(from); + msg.setGroupId(groupid); + msg.setGroupName(groupname); + msg.setReason(reason); + msg.setTime(time); + msg.setGroupInviter(groupInviter); + + if(status == InviteMesageStatus.BEINVITEED.ordinal()) + msg.setStatus(InviteMesageStatus.BEINVITEED); + else if(status == InviteMesageStatus.BEAGREED.ordinal()) + msg.setStatus(InviteMesageStatus.BEAGREED); + else if(status == InviteMesageStatus.BEREFUSED.ordinal()) + msg.setStatus(InviteMesageStatus.BEREFUSED); + else if(status == InviteMesageStatus.AGREED.ordinal()) + msg.setStatus(InviteMesageStatus.AGREED); + else if(status == InviteMesageStatus.REFUSED.ordinal()) + msg.setStatus(InviteMesageStatus.REFUSED); + else if(status == InviteMesageStatus.BEAPPLYED.ordinal()) + msg.setStatus(InviteMesageStatus.BEAPPLYED); + else if(status == InviteMesageStatus.GROUPINVITATION.ordinal()) + msg.setStatus(InviteMesageStatus.GROUPINVITATION); + else if(status == InviteMesageStatus.GROUPINVITATION_ACCEPTED.ordinal()) + msg.setStatus(InviteMesageStatus.GROUPINVITATION_ACCEPTED); + else if(status == InviteMesageStatus.GROUPINVITATION_DECLINED.ordinal()) + msg.setStatus(InviteMesageStatus.GROUPINVITATION_DECLINED); + + msgs.add(msg); + } + cursor.close(); + } + return msgs; + } + + /** + * delete invitation message + * @param from + */ + synchronized public void deleteMessage(String from){ + SQLiteDatabase db = dbHelper.getWritableDatabase(); + if(db.isOpen()){ + db.delete(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.TABLE_NAME, com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_FROM + " = ?", new String[]{from}); + } + } + + synchronized int getUnreadNotifyCount(){ + int count = 0; + SQLiteDatabase db = dbHelper.getReadableDatabase(); + if(db.isOpen()){ + Cursor cursor = db.rawQuery("select " + com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_UNREAD_MSG_COUNT + " from " + com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.TABLE_NAME, null); + if(cursor.moveToFirst()){ + count = cursor.getInt(0); + } + cursor.close(); + } + return count; + } + + synchronized void setUnreadNotifyCount(int count){ + SQLiteDatabase db = dbHelper.getWritableDatabase(); + if(db.isOpen()){ + ContentValues values = new ContentValues(); + values.put(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.COLUMN_NAME_UNREAD_MSG_COUNT, count); + + db.update(com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao.TABLE_NAME, values, null,null); + } + } + + synchronized public void closeDB(){ + if(dbHelper != null){ + dbHelper.closeDB(); + } + dbMgr = null; + } + + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/db/InviteMessgeDao.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/db/InviteMessgeDao.java new file mode 100644 index 0000000..68231dd --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/db/InviteMessgeDao.java @@ -0,0 +1,80 @@ +/** + * Copyright (C) 2016 Hyphenate Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.nanchen.aiyaschoolpush.db; + +import android.content.ContentValues; +import android.content.Context; + +import com.example.nanchen.aiyaschoolpush.im.InviteMessage; + +import java.util.List; + + +public class InviteMessgeDao { + static final String TABLE_NAME = "new_friends_msgs"; + static final String COLUMN_NAME_ID = "id"; + static final String COLUMN_NAME_FROM = "username"; + static final String COLUMN_NAME_GROUP_ID = "groupid"; + static final String COLUMN_NAME_GROUP_Name = "groupname"; + + static final String COLUMN_NAME_TIME = "time"; + static final String COLUMN_NAME_REASON = "reason"; + public static final String COLUMN_NAME_STATUS = "status"; + static final String COLUMN_NAME_ISINVITEFROMME = "isInviteFromMe"; + static final String COLUMN_NAME_GROUPINVITER = "groupinviter"; + + static final String COLUMN_NAME_UNREAD_MSG_COUNT = "unreadMsgCount"; + + + public InviteMessgeDao(Context context){ + } + + /** + * save message + * @param message + * @return return cursor of the message + */ + public Integer saveMessage(InviteMessage message){ + return DemoDBManager.getInstance().saveMessage(message); + } + + /** + * update message + * @param msgId + * @param values + */ + public void updateMessage(int msgId,ContentValues values){ + DemoDBManager.getInstance().updateMessage(msgId, values); + } + + /** + * get messges + * @return + */ + public List getMessagesList(){ + return DemoDBManager.getInstance().getMessagesList(); + } + + public void deleteMessage(String from){ + DemoDBManager.getInstance().deleteMessage(from); + } + + public int getUnreadMessagesCount(){ + return DemoDBManager.getInstance().getUnreadNotifyCount(); + } + + public void saveUnreadMessageCount(int count){ + DemoDBManager.getInstance().setUnreadNotifyCount(count); + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/db/UserDao.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/db/UserDao.java new file mode 100644 index 0000000..7274e97 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/db/UserDao.java @@ -0,0 +1,93 @@ +/** + * Copyright (C) 2016 Hyphenate Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.nanchen.aiyaschoolpush.db; + +import android.content.Context; + +import com.hyphenate.easeui.domain.EaseUser; + +import java.util.List; +import java.util.Map; + +public class UserDao { + public static final String TABLE_NAME = "uers"; + public static final String COLUMN_NAME_ID = "username"; + public static final String COLUMN_NAME_NICK = "nick"; + public static final String COLUMN_NAME_AVATAR = "avatar"; + + public static final String PREF_TABLE_NAME = "pref"; + public static final String COLUMN_NAME_DISABLED_GROUPS = "disabled_groups"; + public static final String COLUMN_NAME_DISABLED_IDS = "disabled_ids"; + + public static final String ROBOT_TABLE_NAME = "robots"; + public static final String ROBOT_COLUMN_NAME_ID = "username"; + public static final String ROBOT_COLUMN_NAME_NICK = "nick"; + public static final String ROBOT_COLUMN_NAME_AVATAR = "avatar"; + + + public UserDao(Context context) { + } + + /** + * save contact list + * + * @param contactList + */ + public void saveContactList(List contactList) { + DemoDBManager.getInstance().saveContactList(contactList); + } + + /** + * get contact list + * + * @return + */ + public Map getContactList() { + + return DemoDBManager.getInstance().getContactList(); + } + + /** + * delete a contact + * @param username + */ + public void deleteContact(String username){ + DemoDBManager.getInstance().deleteContact(username); + } + + /** + * save a contact + * @param user + */ + public void saveContact(EaseUser user){ + DemoDBManager.getInstance().saveContact(user); + } + + public void setDisabledGroups(List groups){ + DemoDBManager.getInstance().setDisabledGroups(groups); + } + + public List getDisabledGroups(){ + return DemoDBManager.getInstance().getDisabledGroups(); + } + + public void setDisabledIds(List ids){ + DemoDBManager.getInstance().setDisabledIds(ids); + } + + public List getDisabledIds(){ + return DemoDBManager.getInstance().getDisabledIds(); + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/CallReceiver.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/CallReceiver.java new file mode 100644 index 0000000..5e213e2 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/CallReceiver.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2016 Hyphenate Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.nanchen.aiyaschoolpush.helper; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.example.nanchen.aiyaschoolpush.ui.activity.VideoCallActivity; +import com.example.nanchen.aiyaschoolpush.ui.activity.VoiceCallActivity; +import com.hyphenate.util.EMLog; + +public class CallReceiver extends BroadcastReceiver{ + + @Override + public void onReceive(Context context, Intent intent) { + if(!DemoHelper.getInstance().isLoggedIn()) + return; + //username + String from = intent.getStringExtra("from"); + //call type + String type = intent.getStringExtra("type"); + if("video".equals(type)){ //video call + context.startActivity(new Intent(context, VideoCallActivity.class). + putExtra("username", from).putExtra("isComingCall", true). + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + }else{ //voice call + context.startActivity(new Intent(context, VoiceCallActivity.class). + putExtra("username", from).putExtra("isComingCall", true). + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } + EMLog.d("CallReceiver", "app received a incoming call"); + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/CoderHelper.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/CoderHelper.java new file mode 100644 index 0000000..c6040a3 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/CoderHelper.java @@ -0,0 +1,19 @@ +package com.example.nanchen.aiyaschoolpush.helper; + +import java.io.Closeable; + +/** + * 语法简化器 + * + * */ +public class CoderHelper { + public static void close(Closeable closable) { + if (closable != null) { + try { + closable.close(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/DemoHelper.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/DemoHelper.java new file mode 100644 index 0000000..12d3d47 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/DemoHelper.java @@ -0,0 +1,1245 @@ +package com.example.nanchen.aiyaschoolpush.helper; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; +import android.widget.Toast; + +import com.example.nanchen.aiyaschoolpush.App; +import com.example.nanchen.aiyaschoolpush.R; +import com.example.nanchen.aiyaschoolpush.ui.activity.ChatActivity; +import com.example.nanchen.aiyaschoolpush.ui.activity.MainActivity; +import com.example.nanchen.aiyaschoolpush.ui.activity.VideoCallActivity; +import com.example.nanchen.aiyaschoolpush.ui.activity.VoiceCallActivity; +import com.example.nanchen.aiyaschoolpush.db.DemoDBManager; +import com.example.nanchen.aiyaschoolpush.db.InviteMessgeDao; +import com.example.nanchen.aiyaschoolpush.db.UserDao; +import com.example.nanchen.aiyaschoolpush.im.Constant; +import com.example.nanchen.aiyaschoolpush.im.DataSyncListener; +import com.example.nanchen.aiyaschoolpush.im.EmojiconExampleGroupData; +import com.example.nanchen.aiyaschoolpush.im.InviteMesageStatus; +import com.example.nanchen.aiyaschoolpush.im.InviteMessage; +import com.example.nanchen.aiyaschoolpush.im.PreferenceManager; +import com.example.nanchen.aiyaschoolpush.im.UserProfileManager; +import com.example.nanchen.aiyaschoolpush.model.DemoModel; +import com.hyphenate.EMCallBack; +import com.hyphenate.EMConnectionListener; +import com.hyphenate.EMContactListener; +import com.hyphenate.EMError; +import com.hyphenate.EMGroupChangeListener; +import com.hyphenate.EMMessageListener; +import com.hyphenate.EMValueCallBack; +import com.hyphenate.chat.EMClient; +import com.hyphenate.chat.EMCmdMessageBody; +import com.hyphenate.chat.EMGroup; +import com.hyphenate.chat.EMMessage; +import com.hyphenate.chat.EMMessage.ChatType; +import com.hyphenate.chat.EMMessage.Status; +import com.hyphenate.chat.EMMessage.Type; +import com.hyphenate.chat.EMOptions; +import com.hyphenate.chat.EMTextMessageBody; +import com.hyphenate.easeui.controller.EaseUI; +import com.hyphenate.easeui.controller.EaseUI.EaseEmojiconInfoProvider; +import com.hyphenate.easeui.controller.EaseUI.EaseSettingsProvider; +import com.hyphenate.easeui.controller.EaseUI.EaseUserProfileProvider; +import com.hyphenate.easeui.domain.EaseEmojicon; +import com.hyphenate.easeui.domain.EaseEmojiconGroupEntity; +import com.hyphenate.easeui.domain.EaseUser; +import com.hyphenate.easeui.model.EaseAtMessageHelper; +import com.hyphenate.easeui.model.EaseNotifier; +import com.hyphenate.easeui.model.EaseNotifier.EaseNotificationInfoProvider; +import com.hyphenate.easeui.utils.EaseCommonUtils; +import com.hyphenate.exceptions.HyphenateException; +import com.hyphenate.util.EMLog; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.helper + * @date 2016/10/28 08:49 + */ + +public class DemoHelper { + + protected static final String TAG = "DemoHelper"; + + private EaseUI easeUI; + + /** + * EMEventListener + */ + protected EMMessageListener messageListener = null; + + private Map contactList; + + private UserProfileManager userProManager; + + private static DemoHelper instance = null; + + private DemoModel demoModel = null; + + /** + * sync groups status listener + */ + private List syncGroupsListeners; + /** + * sync contacts status listener + */ + private List syncContactsListeners; + /** + * sync blacklist status listener + */ + private List syncBlackListListeners; + + private boolean isSyncingGroupsWithServer = false; + private boolean isSyncingContactsWithServer = false; + private boolean isSyncingBlackListWithServer = false; + private boolean isGroupsSyncedWithServer = false; + private boolean isContactsSyncedWithServer = false; + private boolean isBlackListSyncedWithServer = false; + + public boolean isVoiceCalling; + public boolean isVideoCalling; + + private String username; + + private Context appContext; + + private CallReceiver callReceiver; + + private InviteMessgeDao inviteMessgeDao; + private UserDao userDao; + + private LocalBroadcastManager broadcastManager; + + private boolean isGroupAndContactListenerRegisted; + + private DemoHelper() { + } + + public synchronized static DemoHelper getInstance() { + if (instance == null) { + instance = new DemoHelper(); + } + return instance; + } + + /** + * init helper + * + * @param context + * application context + */ + public void init(Context context) { + demoModel = new DemoModel(context); + EMOptions options = initChatOptions(); + //use default options if options is null + if (EaseUI.getInstance().init(context, options)) { + appContext = context; + + //debug mode, you'd better set it to false, if you want release your App officially. + EMClient.getInstance().setDebugMode(true); + //get easeui instance + easeUI = EaseUI.getInstance(); + //to set user's profile and avatar + setEaseUIProviders(); + //initialize preference manager + PreferenceManager.init(context); + //initialize profile manager + getUserProfileManager().init(context); + +// EMClient.getInstance().callManager().getCallOptions().setIsSendPushIfOffline(getModel().isPushCall()); + EMClient.getInstance().callManager().getCallOptions().setIsSendPushIfOffline(true); + + setGlobalListeners(); + broadcastManager = LocalBroadcastManager.getInstance(appContext); + initDbDao(); + + // 初始化easeUI + easeUI.init(App.getAppContext(),options); + + EMClient.getInstance().callManager().getCallOptions().setIsSendPushIfOffline(true);// 设置离线推送为真 + } + } + + + private EMOptions initChatOptions(){ + Log.d(TAG, "init HuanXin Options"); + + EMOptions options = new EMOptions(); + // set if accept the invitation automatically + options.setAcceptInvitationAlways(false); + // set if you need read ack + options.setRequireAck(true); + // set if you need delivery ack + options.setRequireDeliveryAck(false); + + //you need apply & set your own id if you want to use google cloud messaging. + options.setGCMNumber("324169311137"); + //you need apply & set your own id if you want to use Mi push notification + + + options.setMipushConfig("2882303761517426801", "5381742660801"); + + //you need apply & set your own id if you want to use Huawei push notification + options.setHuaweiPushAppId("10492024"); + + //set custom servers, commonly used in private deployment + if(demoModel.isCustomServerEnable() && demoModel.getRestServer() != null && demoModel.getIMServer() != null) { + options.setRestServer(demoModel.getRestServer()); + options.setIMServer(demoModel.getIMServer()); + if(demoModel.getIMServer().contains(":")) { + options.setIMServer(demoModel.getIMServer().split(":")[0]); + options.setImPort(Integer.valueOf(demoModel.getIMServer().split(":")[1])); + } + } + + if (demoModel.isCustomAppkeyEnabled() && demoModel.getCutomAppkey() != null && !demoModel.getCutomAppkey().isEmpty()) { + options.setAppKey(demoModel.getCutomAppkey()); + } + + options.allowChatroomOwnerLeave(getModel().isChatroomOwnerLeaveAllowed()); + options.setDeleteMessagesAsExitGroup(getModel().isDeleteMessagesAsExitGroup()); + options.setAutoAcceptGroupInvitation(getModel().isAutoAcceptGroupInvitation()); + + + return options; + } + + protected void setEaseUIProviders() { + // set profile provider if you want easeUI to handle avatar and nickname + easeUI.setUserProfileProvider(new EaseUserProfileProvider() { + + @Override + public EaseUser getUser(String username) { + return getUserInfo(username); + } + }); + + //set options + easeUI.setSettingsProvider(new EaseSettingsProvider() { + + @Override + public boolean isSpeakerOpened() { + return demoModel.getSettingMsgSpeaker(); + } + + @Override + public boolean isMsgVibrateAllowed(EMMessage message) { + return demoModel.getSettingMsgVibrate(); + } + + @Override + public boolean isMsgSoundAllowed(EMMessage message) { + return demoModel.getSettingMsgSound(); + } + + @Override + public boolean isMsgNotifyAllowed(EMMessage message) { + if(message == null){ + return demoModel.getSettingMsgNotification(); + } + if(!demoModel.getSettingMsgNotification()){ + return false; + }else{ + String chatUsename = null; + List notNotifyIds = null; + // get user or group id which was blocked to show message notifications + if (message.getChatType() == ChatType.Chat) { + chatUsename = message.getFrom(); + notNotifyIds = demoModel.getDisabledIds(); + } else { + chatUsename = message.getTo(); + notNotifyIds = demoModel.getDisabledGroups(); + } + + if (notNotifyIds == null || !notNotifyIds.contains(chatUsename)) { + return true; + } else { + return false; + } + } + } + }); + //set emoji icon provider + easeUI.setEmojiconInfoProvider(new EaseEmojiconInfoProvider() { + + @Override + public EaseEmojicon getEmojiconInfo(String emojiconIdentityCode) { + EaseEmojiconGroupEntity data = EmojiconExampleGroupData.getData(); + for(EaseEmojicon emojicon : data.getEmojiconList()){ + if(emojicon.getIdentityCode().equals(emojiconIdentityCode)){ + return emojicon; + } + } + return null; + } + + @Override + public Map getTextEmojiconMapping() { + return null; + } + }); + + //set notification options, will use default if you don't set it + easeUI.getNotifier().setNotificationInfoProvider(new EaseNotificationInfoProvider() { + + @Override + public String getTitle(EMMessage message) { + //you can update title here + return null; + } + + @Override + public int getSmallIcon(EMMessage message) { + //you can update icon here + return 0; + } + + @Override + public String getDisplayedText(EMMessage message) { + // be used on notification bar, different text according the message type. + String ticker = EaseCommonUtils.getMessageDigest(message, appContext); + if(message.getType() == Type.TXT){ + ticker = ticker.replaceAll("\\[.{2,3}\\]", "[表情]"); + } + EaseUser user = getUserInfo(message.getFrom()); + if(user != null){ + if(EaseAtMessageHelper.get().isAtMeMsg(message)){ + return String.format(appContext.getString(R.string.at_your_in_group), user.getNick()); + } + return user.getNick() + ": " + ticker; + }else{ + if(EaseAtMessageHelper.get().isAtMeMsg(message)){ + return String.format(appContext.getString(R.string.at_your_in_group), message.getFrom()); + } + return message.getFrom() + ": " + ticker; + } + } + + @Override + public String getLatestText(EMMessage message, int fromUsersNum, int messageNum) { + // here you can customize the text. + // return fromUsersNum + "contacts send " + messageNum + "messages to you"; + return null; + } + + @Override + public Intent getLaunchIntent(EMMessage message) { + // you can set what activity you want display when user click the notification + Intent intent = new Intent(appContext, ChatActivity.class); + // open calling activity if there is call + if(isVideoCalling){ + intent = new Intent(appContext, VideoCallActivity.class); + }else if(isVoiceCalling){ + intent = new Intent(appContext, VoiceCallActivity.class); + }else{ + ChatType chatType = message.getChatType(); + if (chatType == ChatType.Chat) { // single chat message + intent.putExtra("userId", message.getFrom()); + intent.putExtra("chatType", Constant.CHATTYPE_SINGLE); + } else { // group chat message + // message.getTo() is the group id + intent.putExtra("userId", message.getTo()); + if(chatType == ChatType.GroupChat){ + intent.putExtra("chatType", Constant.CHATTYPE_GROUP); + }else{ + intent.putExtra("chatType", Constant.CHATTYPE_CHATROOM); + } + + } + } + return intent; + } + }); + } + + EMConnectionListener connectionListener; + /** + * set global listener + */ + protected void setGlobalListeners(){ + syncGroupsListeners = new ArrayList(); + syncContactsListeners = new ArrayList(); + syncBlackListListeners = new ArrayList(); + + isGroupsSyncedWithServer = demoModel.isGroupsSynced(); + isContactsSyncedWithServer = demoModel.isContactSynced(); + isBlackListSyncedWithServer = demoModel.isBacklistSynced(); + + // create the global connection listener + connectionListener = new EMConnectionListener() { + @Override + public void onDisconnected(int error) { + EMLog.d("global listener", "onDisconnect" + error); + + Log.e("abc",error+""); + if (error == EMError.USER_REMOVED) { + onUserException(Constant.ACCOUNT_REMOVED); + } else if (error == EMError.USER_LOGIN_ANOTHER_DEVICE) { + onUserException(Constant.ACCOUNT_CONFLICT); + } else if (error == EMError.SERVER_SERVICE_RESTRICTED) { + onUserException(Constant.ACCOUNT_FORBIDDEN); + } + } + + @Override + public void onConnected() { + // in case group and contact were already synced, we supposed to notify sdk we are ready to receive the events + if (isGroupsSyncedWithServer && isContactsSyncedWithServer) { + EMLog.d(TAG, "group and contact already synced with servre"); + } else { + if (!isGroupsSyncedWithServer) { + asyncFetchGroupsFromServer(null); + } + + if (!isContactsSyncedWithServer) { + asyncFetchContactsFromServer(null); + } + + if (!isBlackListSyncedWithServer) { + asyncFetchBlackListFromServer(null); + } + } + } + }; + + IntentFilter callFilter = new IntentFilter(EMClient.getInstance().callManager().getIncomingCallBroadcastAction()); + if(callReceiver == null){ + callReceiver = new CallReceiver(); + } + + //register incoming call receiver + appContext.registerReceiver(callReceiver, callFilter); + //register connection listener + EMClient.getInstance().addConnectionListener(connectionListener); + //register group and contact event listener + registerGroupAndContactListener(); + //register message event listener + registerMessageListener(); + + } + + private void initDbDao() { + inviteMessgeDao = new InviteMessgeDao(appContext); + userDao = new UserDao(appContext); + } + + /** + * register group and contact listener, you need register when login + */ + public void registerGroupAndContactListener(){ + if(!isGroupAndContactListenerRegisted){ + EMClient.getInstance().groupManager().addGroupChangeListener(new MyGroupChangeListener()); + EMClient.getInstance().contactManager().setContactListener(new MyContactListener()); + isGroupAndContactListenerRegisted = true; + } + + } + + /** + * group change listener + */ + class MyGroupChangeListener implements EMGroupChangeListener { + + @Override + public void onInvitationReceived(String groupId, String groupName, String inviter, String reason) { + + new InviteMessgeDao(appContext).deleteMessage(groupId); + + // user invite you to join group + InviteMessage msg = new InviteMessage(); + msg.setFrom(groupId); + msg.setTime(System.currentTimeMillis()); + msg.setGroupId(groupId); + msg.setGroupName(groupName); + msg.setReason(reason); + msg.setGroupInviter(inviter); + Log.d(TAG, "receive invitation to join the group:" + groupName); + msg.setStatus(InviteMesageStatus.GROUPINVITATION); + notifyNewInviteMessage(msg); + broadcastManager.sendBroadcast(new Intent(Constant.ACTION_GROUP_CHANAGED)); + } + + @Override + public void onInvitationAccepted(String groupId, String invitee, String reason) { + + new InviteMessgeDao(appContext).deleteMessage(groupId); + + //user accept your invitation + boolean hasGroup = false; + EMGroup _group = null; + for (EMGroup group : EMClient.getInstance().groupManager().getAllGroups()) { + if (group.getGroupId().equals(groupId)) { + hasGroup = true; + _group = group; + break; + } + } + if (!hasGroup) + return; + + InviteMessage msg = new InviteMessage(); + msg.setFrom(groupId); + msg.setTime(System.currentTimeMillis()); + msg.setGroupId(groupId); + msg.setGroupName(_group == null ? groupId : _group.getGroupName()); + msg.setReason(reason); + msg.setGroupInviter(invitee); + Log.d(TAG, invitee + "Accept to join the group:" + _group == null ? groupId : _group.getGroupName()); + msg.setStatus(InviteMesageStatus.GROUPINVITATION_ACCEPTED); + notifyNewInviteMessage(msg); + broadcastManager.sendBroadcast(new Intent(Constant.ACTION_GROUP_CHANAGED)); + } + + @Override + public void onInvitationDeclined(String groupId, String invitee, String reason) { + + new InviteMessgeDao(appContext).deleteMessage(groupId); + + //user declined your invitation + EMGroup group = null; + for (EMGroup _group : EMClient.getInstance().groupManager().getAllGroups()) { + if (_group.getGroupId().equals(groupId)) { + group = _group; + break; + } + } + if (group == null) + return; + + InviteMessage msg = new InviteMessage(); + msg.setFrom(groupId); + msg.setTime(System.currentTimeMillis()); + msg.setGroupId(groupId); + msg.setGroupName(group.getGroupName()); + msg.setReason(reason); + msg.setGroupInviter(invitee); + Log.d(TAG, invitee + "Declined to join the group:" + group.getGroupName()); + msg.setStatus(InviteMesageStatus.GROUPINVITATION_DECLINED); + notifyNewInviteMessage(msg); + broadcastManager.sendBroadcast(new Intent(Constant.ACTION_GROUP_CHANAGED)); + } + + @Override + public void onUserRemoved(String groupId, String groupName) { + //user is removed from group + broadcastManager.sendBroadcast(new Intent(Constant.ACTION_GROUP_CHANAGED)); + } + + @Override + public void onGroupDestroyed(String groupId, String groupName) { + // group is dismissed, + broadcastManager.sendBroadcast(new Intent(Constant.ACTION_GROUP_CHANAGED)); + } + + @Override + public void onApplicationReceived(String groupId, String groupName, String applyer, String reason) { + + // user apply to join group + InviteMessage msg = new InviteMessage(); + msg.setFrom(applyer); + msg.setTime(System.currentTimeMillis()); + msg.setGroupId(groupId); + msg.setGroupName(groupName); + msg.setReason(reason); + Log.d(TAG, applyer + " Apply to join group:" + groupName); + msg.setStatus(InviteMesageStatus.BEAPPLYED); + notifyNewInviteMessage(msg); + broadcastManager.sendBroadcast(new Intent(Constant.ACTION_GROUP_CHANAGED)); + } + + @Override + public void onApplicationAccept(String groupId, String groupName, String accepter) { + + String st4 = appContext.getString(R.string.Agreed_to_your_group_chat_application); + // your application was accepted + EMMessage msg = EMMessage.createReceiveMessage(Type.TXT); + msg.setChatType(ChatType.GroupChat); + msg.setFrom(accepter); + msg.setTo(groupId); + msg.setMsgId(UUID.randomUUID().toString()); + msg.addBody(new EMTextMessageBody(accepter + " " +st4)); + msg.setStatus(Status.SUCCESS); + // save accept message + EMClient.getInstance().chatManager().saveMessage(msg); + // notify the accept message + getNotifier().vibrateAndPlayTone(msg); + + broadcastManager.sendBroadcast(new Intent(Constant.ACTION_GROUP_CHANAGED)); + } + + @Override + public void onApplicationDeclined(String groupId, String groupName, String decliner, String reason) { + // your application was declined, we do nothing here in demo + } + + @Override + public void onAutoAcceptInvitationFromGroup(String groupId, String inviter, String inviteMessage) { + // got an invitation + String st3 = appContext.getString(R.string.Invite_you_to_join_a_group_chat); + EMMessage msg = EMMessage.createReceiveMessage(Type.TXT); + msg.setChatType(ChatType.GroupChat); + msg.setFrom(inviter); + msg.setTo(groupId); + msg.setMsgId(UUID.randomUUID().toString()); + msg.addBody(new EMTextMessageBody(inviter + " " +st3)); + msg.setStatus(EMMessage.Status.SUCCESS); + // save invitation as messages + EMClient.getInstance().chatManager().saveMessage(msg); + // notify invitation message + getNotifier().vibrateAndPlayTone(msg); + EMLog.d(TAG, "onAutoAcceptInvitationFromGroup groupId:" + groupId); + broadcastManager.sendBroadcast(new Intent(Constant.ACTION_GROUP_CHANAGED)); + } + } + + /*** + * 好友变化listener + * + */ + public class MyContactListener implements EMContactListener { + + @Override + public void onContactAdded(String username) { + // save contact + Map localUsers = getContactList(); + Map toAddUsers = new HashMap(); + EaseUser user = new EaseUser(username); + + if (!localUsers.containsKey(username)) { + userDao.saveContact(user); + } + toAddUsers.put(username, user); + localUsers.putAll(toAddUsers); + + broadcastManager.sendBroadcast(new Intent(Constant.ACTION_CONTACT_CHANAGED)); + } + + @Override + public void onContactDeleted(String username) { + Map localUsers = DemoHelper.getInstance().getContactList(); + localUsers.remove(username); + userDao.deleteContact(username); + inviteMessgeDao.deleteMessage(username); + + broadcastManager.sendBroadcast(new Intent(Constant.ACTION_CONTACT_CHANAGED)); + } + + @Override + public void onContactInvited(String username, String reason) { + List msgs = inviteMessgeDao.getMessagesList(); + + for (InviteMessage inviteMessage : msgs) { + if (inviteMessage.getGroupId() == null && inviteMessage.getFrom().equals(username)) { + inviteMessgeDao.deleteMessage(username); + } + } + // save invitation as message + InviteMessage msg = new InviteMessage(); + msg.setFrom(username); + msg.setTime(System.currentTimeMillis()); + msg.setReason(reason); + Log.d(TAG, username + "apply to be your friend,reason: " + reason); + // set invitation status + msg.setStatus(InviteMesageStatus.BEINVITEED); + notifyNewInviteMessage(msg); + broadcastManager.sendBroadcast(new Intent(Constant.ACTION_CONTACT_CHANAGED)); + } + + @Override + public void onContactAgreed(String username) { + List msgs = inviteMessgeDao.getMessagesList(); + for (InviteMessage inviteMessage : msgs) { + if (inviteMessage.getFrom().equals(username)) { + return; + } + } + // save invitation as message + InviteMessage msg = new InviteMessage(); + msg.setFrom(username); + msg.setTime(System.currentTimeMillis()); + Log.d(TAG, username + "accept your request"); + msg.setStatus(InviteMesageStatus.BEAGREED); + notifyNewInviteMessage(msg); + broadcastManager.sendBroadcast(new Intent(Constant.ACTION_CONTACT_CHANAGED)); + } + + @Override + public void onContactRefused(String username) { + // your request was refused + Log.d(username, username + " refused to your request"); + } + } + + /** + * save and notify invitation message + * @param msg + */ + private void notifyNewInviteMessage(InviteMessage msg){ + if(inviteMessgeDao == null){ + inviteMessgeDao = new InviteMessgeDao(appContext); + } + inviteMessgeDao.saveMessage(msg); + //increase the unread message count + inviteMessgeDao.saveUnreadMessageCount(1); + // notify there is new message + getNotifier().vibrateAndPlayTone(null); + } + + /** + * user met some exception: conflict, removed or forbidden + */ + protected void onUserException(String exception){ + EMLog.e(TAG, "onUserException: " + exception); + Intent intent = new Intent(appContext, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(exception, true); + appContext.startActivity(intent); + } + + private EaseUser getUserInfo(String username){ + // To get instance of EaseUser, here we get it from the user list in memory + // You'd better cache it if you get it from your server + EaseUser user = null; + if(username.equals(EMClient.getInstance().getCurrentUser())) + return getUserProfileManager().getCurrentUserInfo(); + user = getContactList().get(username); +// if(user == null && getRobotList() != null){ +// user = getRobotList().get(username); +// } + + // if user is not in your contacts, set inital letter for him/her + if(user == null){ + user = new EaseUser(username); + EaseCommonUtils.setUserInitialLetter(user); + } + return user; + } + + /** + * Global listener + * If this event already handled by an activity, you don't need handle it again + * activityList.size() <= 0 means all activities already in background or not in Activity Stack + */ + protected void registerMessageListener() { + messageListener = new EMMessageListener() { + private BroadcastReceiver broadCastReceiver = null; + + @Override + public void onMessageReceived(List messages) { + for (EMMessage message : messages) { + EMLog.d(TAG, "onMessageReceived id : " + message.getMsgId()); + // in background, do not refresh UI, notify it in notification bar + if(!easeUI.hasForegroundActivies()){ + getNotifier().onNewMsg(message); + } + } + } + + @Override + public void onCmdMessageReceived(List messages) { + for (EMMessage message : messages) { + EMLog.d(TAG, "receive command message"); + //get message body + EMCmdMessageBody cmdMsgBody = (EMCmdMessageBody) message.getBody(); + final String action = cmdMsgBody.action();//获取自定义action + //red packet code : 处理红包回执透传消息 + if(!easeUI.hasForegroundActivies()){ +// if (action.equals(RPConstant.REFRESH_GROUP_RED_PACKET_ACTION)){ +// RedPacketUtil.receiveRedPacketAckMessage(message); +// broadcastManager.sendBroadcast(new Intent(RPConstant.REFRESH_GROUP_RED_PACKET_ACTION)); +// } + } + + if (action.equals("__Call_ReqP2P_ConferencePattern")) { + String title = message.getStringAttribute("em_apns_ext", "conference call"); + Toast.makeText(appContext, title, Toast.LENGTH_LONG).show(); + } + //end of red packet code + //获取扩展属性 此处省略 + //maybe you need get extension of your message + //message.getStringAttribute(""); + EMLog.d(TAG, String.format("Command:action:%s,message:%s", action,message.toString())); + } + } + + @Override + public void onMessageReadAckReceived(List messages) { + } + + @Override + public void onMessageDeliveryAckReceived(List message) { + } + + @Override + public void onMessageChanged(EMMessage message, Object change) { + + } + }; + + EMClient.getInstance().chatManager().addMessageListener(messageListener); + } + + /** + * if ever logged in + * + * @return + */ + public boolean isLoggedIn() { + return EMClient.getInstance().isLoggedInBefore(); + } + + /** + * logout + * + * @param unbindDeviceToken + * whether you need unbind your device token + * @param callback + * callback + */ + public void logout(boolean unbindDeviceToken, final EMCallBack callback) { + endCall(); + Log.d(TAG, "logout: " + unbindDeviceToken); + EMClient.getInstance().logout(unbindDeviceToken, new EMCallBack() { + + @Override + public void onSuccess() { + Log.d(TAG, "logout: onSuccess"); + reset(); + if (callback != null) { + callback.onSuccess(); + } + + } + + @Override + public void onProgress(int progress, String status) { + if (callback != null) { + callback.onProgress(progress, status); + } + } + + @Override + public void onError(int code, String error) { + Log.d(TAG, "logout: onSuccess"); + reset(); + if (callback != null) { + callback.onError(code, error); + } + } + }); + } + + /** + * get instance of EaseNotifier + * @return + */ + public EaseNotifier getNotifier(){ + return easeUI.getNotifier(); + } + + public DemoModel getModel(){ + return (DemoModel) demoModel; + } + + /** + * update contact list + * + * @param aContactList + */ + public void setContactList(Map aContactList) { + if(aContactList == null){ + if (contactList != null) { + contactList.clear(); + } + return; + } + + contactList = aContactList; + } + + /** + * save single contact + */ + public void saveContact(EaseUser user){ + contactList.put(user.getUsername(), user); + demoModel.saveContact(user); + } + + /** + * get contact list + * + * @return + */ + public Map getContactList() { + if (isLoggedIn() && contactList == null) { + contactList = demoModel.getContactList(); + } + + // return a empty non-null object to avoid app crash + if(contactList == null){ + return new Hashtable(); + } + + return contactList; + } + + /** + * set current username + * @param username + */ + public void setCurrentUserName(String username){ + this.username = username; + demoModel.setCurrentUserName(username); + } + + /** + * get current user's id + */ + public String getCurrentUserName(){ + if(username == null){ + username = demoModel.getCurrentUsernName(); + } + return username; + } + + + /** + * update user list to cache and database + * + * @param contactInfoList + */ + public void updateContactList(List contactInfoList) { + for (EaseUser u : contactInfoList) { + contactList.put(u.getUsername(), u); + } + ArrayList mList = new ArrayList(); + mList.addAll(contactList.values()); + demoModel.saveContactList(mList); + } + + public UserProfileManager getUserProfileManager() { + if (userProManager == null) { + userProManager = new UserProfileManager(); + } + return userProManager; + } + + void endCall() { + try { + EMClient.getInstance().callManager().endCall(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void addSyncGroupListener(DataSyncListener listener) { + if (listener == null) { + return; + } + if (!syncGroupsListeners.contains(listener)) { + syncGroupsListeners.add(listener); + } + } + + public void removeSyncGroupListener(DataSyncListener listener) { + if (listener == null) { + return; + } + if (syncGroupsListeners.contains(listener)) { + syncGroupsListeners.remove(listener); + } + } + + public void addSyncContactListener(DataSyncListener listener) { + if (listener == null) { + return; + } + if (!syncContactsListeners.contains(listener)) { + syncContactsListeners.add(listener); + } + } + + public void removeSyncContactListener(DataSyncListener listener) { + if (listener == null) { + return; + } + if (syncContactsListeners.contains(listener)) { + syncContactsListeners.remove(listener); + } + } + + public void addSyncBlackListListener(DataSyncListener listener) { + if (listener == null) { + return; + } + if (!syncBlackListListeners.contains(listener)) { + syncBlackListListeners.add(listener); + } + } + + public void removeSyncBlackListListener(DataSyncListener listener) { + if (listener == null) { + return; + } + if (syncBlackListListeners.contains(listener)) { + syncBlackListListeners.remove(listener); + } + } + + /** + * Get group list from server + * This method will save the sync state + * @throws HyphenateException + */ + public synchronized void asyncFetchGroupsFromServer(final EMCallBack callback){ + if(isSyncingGroupsWithServer){ + return; + } + + isSyncingGroupsWithServer = true; + + new Thread(){ + @Override + public void run(){ + try { + EMClient.getInstance().groupManager().getJoinedGroupsFromServer(); + + // in case that logout already before server returns, we should return immediately + if(!isLoggedIn()){ + isGroupsSyncedWithServer = false; + isSyncingGroupsWithServer = false; + noitifyGroupSyncListeners(false); + return; + } + + demoModel.setGroupsSynced(true); + + isGroupsSyncedWithServer = true; + isSyncingGroupsWithServer = false; + + //notify sync group list success + noitifyGroupSyncListeners(true); + + if(callback != null){ + callback.onSuccess(); + } + } catch (HyphenateException e) { + demoModel.setGroupsSynced(false); + isGroupsSyncedWithServer = false; + isSyncingGroupsWithServer = false; + noitifyGroupSyncListeners(false); + if(callback != null){ + callback.onError(e.getErrorCode(), e.toString()); + } + } + + } + }.start(); + } + + public void noitifyGroupSyncListeners(boolean success){ + for (DataSyncListener listener : syncGroupsListeners) { + listener.onSyncComplete(success); + } + } + + public void asyncFetchContactsFromServer(final EMValueCallBack> callback){ + if(isSyncingContactsWithServer){ + return; + } + + isSyncingContactsWithServer = true; + + new Thread(){ + @Override + public void run(){ + List usernames = null; + try { + usernames = EMClient.getInstance().contactManager().getAllContactsFromServer(); + // in case that logout already before server returns, we should return immediately + if(!isLoggedIn()){ + isContactsSyncedWithServer = false; + isSyncingContactsWithServer = false; + notifyContactsSyncListener(false); + return; + } + + Map userlist = new HashMap(); + for (String username : usernames) { + EaseUser user = new EaseUser(username); + EaseCommonUtils.setUserInitialLetter(user); + userlist.put(username, user); + } + // save the contact list to cache + getContactList().clear(); + getContactList().putAll(userlist); + // save the contact list to database + UserDao dao = new UserDao(appContext); + List users = new ArrayList(userlist.values()); + dao.saveContactList(users); + + demoModel.setContactSynced(true); + EMLog.d(TAG, "set contact syn status to true"); + + isContactsSyncedWithServer = true; + isSyncingContactsWithServer = false; + + //notify sync success + notifyContactsSyncListener(true); + + getUserProfileManager().asyncFetchContactInfosFromServer(usernames,new EMValueCallBack>() { + + @Override + public void onSuccess(List uList) { + updateContactList(uList); + getUserProfileManager().notifyContactInfosSyncListener(true); + } + + @Override + public void onError(int error, String errorMsg) { + } + }); + if(callback != null){ + callback.onSuccess(usernames); + } + } catch (HyphenateException e) { + demoModel.setContactSynced(false); + isContactsSyncedWithServer = false; + isSyncingContactsWithServer = false; + notifyContactsSyncListener(false); + e.printStackTrace(); + if(callback != null){ + callback.onError(e.getErrorCode(), e.toString()); + } + } + + } + }.start(); + } + + public void notifyContactsSyncListener(boolean success){ + for (DataSyncListener listener : syncContactsListeners) { + listener.onSyncComplete(success); + } + } + + public void asyncFetchBlackListFromServer(final EMValueCallBack> callback){ + + if(isSyncingBlackListWithServer){ + return; + } + + isSyncingBlackListWithServer = true; + + new Thread(){ + @Override + public void run(){ + try { + List usernames = EMClient.getInstance().contactManager().getBlackListFromServer(); + + // in case that logout already before server returns, we should return immediately + if(!isLoggedIn()){ + isBlackListSyncedWithServer = false; + isSyncingBlackListWithServer = false; + notifyBlackListSyncListener(false); + return; + } + + demoModel.setBlacklistSynced(true); + + isBlackListSyncedWithServer = true; + isSyncingBlackListWithServer = false; + + notifyBlackListSyncListener(true); + if(callback != null){ + callback.onSuccess(usernames); + } + } catch (HyphenateException e) { + demoModel.setBlacklistSynced(false); + + isBlackListSyncedWithServer = false; + isSyncingBlackListWithServer = true; + e.printStackTrace(); + + if(callback != null){ + callback.onError(e.getErrorCode(), e.toString()); + } + } + + } + }.start(); + } + + public void notifyBlackListSyncListener(boolean success){ + for (DataSyncListener listener : syncBlackListListeners) { + listener.onSyncComplete(success); + } + } + + public boolean isSyncingGroupsWithServer() { + return isSyncingGroupsWithServer; + } + + public boolean isSyncingContactsWithServer() { + return isSyncingContactsWithServer; + } + + public boolean isSyncingBlackListWithServer() { + return isSyncingBlackListWithServer; + } + + public boolean isGroupsSyncedWithServer() { + return isGroupsSyncedWithServer; + } + + public boolean isContactsSyncedWithServer() { + return isContactsSyncedWithServer; + } + + public boolean isBlackListSyncedWithServer() { + return isBlackListSyncedWithServer; + } + + synchronized void reset(){ + isSyncingGroupsWithServer = false; + isSyncingContactsWithServer = false; + isSyncingBlackListWithServer = false; + + demoModel.setGroupsSynced(false); + demoModel.setContactSynced(false); + demoModel.setBlacklistSynced(false); + + isGroupsSyncedWithServer = false; + isContactsSyncedWithServer = false; + isBlackListSyncedWithServer = false; + + isGroupAndContactListenerRegisted = false; + + setContactList(null); +// setRobotList(null); + getUserProfileManager().reset(); + DemoDBManager.getInstance().closeDB(); + } + + public void pushActivity(Activity activity) { + easeUI.pushActivity(activity); + } + + public void popActivity(Activity activity) { + easeUI.popActivity(activity); + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/Environment2.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/Environment2.java new file mode 100644 index 0000000..c6ab4ff --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/Environment2.java @@ -0,0 +1,39 @@ +package com.example.nanchen.aiyaschoolpush.helper; + +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; + +import com.example.nanchen.aiyaschoolpush.App; + +/** + * 表示当前应用及系统的环境信息 + * + * */ +public class Environment2 { + /** + * 获取Application Context + * */ + public static Context getAppContext() { + return App.getAppContext(); + } + + public static int getPackageVersionCode() { + try { + Context ctx = App.getAppContext(); + return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0).versionCode; + } catch (NameNotFoundException e) { + e.printStackTrace(); + return 1; + } + } + + public static String getPackageVersionName() { + try { + Context ctx = App.getAppContext(); + return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0).versionName; + } catch (NameNotFoundException e) { + e.printStackTrace(); + return "1.0"; + } + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/FileHelper.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/FileHelper.java new file mode 100644 index 0000000..c6d4175 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/FileHelper.java @@ -0,0 +1,364 @@ +package com.example.nanchen.aiyaschoolpush.helper; + +import android.content.Context; +import android.media.ExifInterface; +import android.os.Environment; +import android.os.StatFs; + +import com.example.nanchen.aiyaschoolpush.App; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * 文件工具类 + * + * @author Frank + * @email frank@mondol.info + * @create_date 2015-2 + */ +public class FileHelper { + + public static final String AUDIO = Environment.DIRECTORY_MUSIC; + public static final String IMAGE = Environment.DIRECTORY_PICTURES; + public static final String OTHER = Environment.DIRECTORY_PICTURES; + + static boolean mExternalStorageAvailable = false; + static boolean mExternalStorageWriteable = false; + + /** + * 检索当前系统是否包含扩展存储卡 + * + * @return 0:没有存储卡|1:有只读存储卡|2:有可读写存储卡 + */ + public static int hasExternalStorage() { + final String state = Environment.getExternalStorageState(); + if (Environment.MEDIA_MOUNTED.equals(state)) { + // We can read and write the media + return 2; + } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { + return 1; + } else { + // 其它状态是错误的,即不能读也不能写 + return 0; + } + } + + /** + * 获取扩展存储卡剩余空间 + */ + public static long getAvailableExternalStorageSize() { + if (2 == hasExternalStorage()) { + StatFs localStatFs = new StatFs(Environment.getExternalStorageDirectory().getPath()); + + long blockSize = localStatFs.getBlockSize(); + long blockCount = localStatFs.getAvailableBlocks(); + return blockSize * blockCount; + } + return 0; + } + + /** + * 复制一个文件 + */ + public static int copyFile(String fromFile, String toFile) { + try { + InputStream fosfrom = new FileInputStream(fromFile); + FileOutputStream fosto = new FileOutputStream(toFile); + byte[] bys = new byte[4096]; + int c; + while ((c = fosfrom.read(bys)) > 0) { + fosto.write(bys, 0, c); + } + fosfrom.close(); + fosto.close(); + return 0; + + } catch (Exception ex) { + return -1; + } + } + + /** + * 获取files目录,优先返回存储卡中的目录 + */ + public static File getFilesDir() { + Context ctx = Environment2.getAppContext(); + if (2 == hasExternalStorage()) { + return ctx.getExternalFilesDir(null); + } else { + return ctx.getFilesDir(); + } + } + + /** + * 获取缓存路径(优先返回存储卡中的目录) + */ + public static File getCacheDir() { + Context ctx = Environment2.getAppContext(); + if (2 == hasExternalStorage()) { + return ctx.getExternalCacheDir(); + } else { + return ctx.getCacheDir(); + } + } + + /** + * 返回指定目录中一个唯一的空文件 + *

+ * 注:方法成功返回时文件已创建,用后请删除 + * + * @param fDir 文件目录 如果为null则会在扩展存储卡中的CacheDir中生成文件 + * 如果扩展存储卡不存在则在应用CacheDir中生成文件 + * @param suffix 文件后缀 null则使用.tmp + * @return 返回则返回null + */ + public static File createTempFile(File fDir, String suffix) { + Context context = Environment2.getAppContext(); + if (fDir == null) { + if (2 == hasExternalStorage()) { + fDir = context.getExternalCacheDir(); + } else { + fDir = context.getCacheDir(); + } + } + + SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHH"); + String prefix = sdf.format(new Date()); + try { + return File.createTempFile(prefix, suffix, fDir); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * 获取一个空的临时文件 + * + * @throws IOException + */ + public static File createTempFile(String suffix) { + return createTempFile(null, suffix); + } + + /** + * 获取随机图片文件名 + * + * @return + * @author Williams + */ + static public File getImageFilePath() { + String suffix = ".jpg"; + File dir = getDirByType(IMAGE); + File file = getFile(suffix, dir); + return file; + } + + /** + * 获取图片和音频外的文件名 + * + * @return + * @author Williams + */ + static public File getOtherFilePath(String sufix) { + String suffix = "." + sufix; + File dir = getDirByType(OTHER); + File file = getFile(suffix, dir); + return file; + } + + /** + * 获取音频随机文件名 + * + * @return + * @author Williams + */ + static public File getAudioFilePath() { + String suffix = ".amr"; + File dir = getDirByType(AUDIO); + File file = getFile(suffix, dir); + return file; + } + + /** + * 获取音频随机文件名 + * + * @return + * @author Williams + */ + public static File getFile(String suffix, File dir) { + if (dir == null) { + throw new NullPointerException("Dir can not be null!"); + } else if (!dir.exists()) { + dir.mkdirs(); + } + try { + return File.createTempFile("fx_temp_", suffix, dir); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * 获取图片文件扩展信息(方向) + * + * @return int 图片的旋转角度 + * @author Williams + */ + public static int getExifOrientation(String filepath) { + int degree = 0; + ExifInterface exif = null; + try { + exif = new ExifInterface(filepath); + } catch (IOException ex) { + } + if (exif != null) { + int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1); + if (orientation != -1) { + // We only recognize a subset of orientation tag values. + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_90: + degree = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + degree = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + degree = 270; + break; + } + + } + } + return degree; + } + + /** + * 检测外部存储卡状态 + * + * @return + * @author Williams + */ + private static void checkStorageState() { + final String state = Environment.getExternalStorageState(); + + if (Environment.MEDIA_MOUNTED.equals(state)) { + // 外部存储卡可读写 + mExternalStorageAvailable = mExternalStorageWriteable = true; + } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { + // 外部存储卡只读 + mExternalStorageAvailable = true; + mExternalStorageWriteable = false; + } else { + // 外部存储卡不可用 + mExternalStorageAvailable = mExternalStorageWriteable = false; + } + } + + private static Context getContext() { + return App.getAppContext().getApplicationContext(); + } + + /** + * 获取音频随机文件 + * + * @return + * @author Williams + */ + public static File getAudioDir() { + return getDirByType(AUDIO); + } + + /** + * 获取图片随机文件 + * + * @return + * @author Williams + */ + public static File getImageDir() { + return getDirByType(IMAGE); + } + + /** + * @return + * @author Williams + */ + static public File getDirByType(String typeName) { + checkStorageState(); + final Context context = getContext(); + if (mExternalStorageWriteable) { + return context.getExternalFilesDir(typeName); + } else { + return context.getCacheDir(); + } + } + + /** + * @return + * @author WLF + */ + public static File getFileDir() { + checkStorageState(); + final Context context = getContext(); + if (mExternalStorageWriteable) { + return context.getExternalFilesDir(null); + } else { + return context.getCacheDir(); + } + } + + /** + * 检测SD卡是否可写 + * + * @return + * @author WLF + */ + public static boolean isSdcardWriteable() { + checkStorageState(); + return mExternalStorageWriteable; + } + + /** + * 检测文件是否可用 + * + * @return + * @author WLF + */ + public static boolean checkFile(File f) { + if (f != null && f.exists() && f.canRead() && (f.isDirectory() || (f.isFile() && f.length() > 0))) { + return true; + } + return false; + } + + /** + * 检测文件是否可用 + * + * @return + * @author WLF + */ + public static boolean checkFile(String path) { + if (StringHelper.isNotEmpty(path)) { + File f = new File(path); + if (f != null && f.exists() && f.canRead() && (f.isDirectory() || (f.isFile() && f.length() > 0))) + return true; + } + return false; + } + + public static boolean tryDeleteFile(File file) { + try { + file.delete(); + return true; + } catch (Throwable t) { + return false; + } + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/FontCustomHelper.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/FontCustomHelper.java new file mode 100644 index 0000000..3f635db --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/FontCustomHelper.java @@ -0,0 +1,39 @@ +package com.example.nanchen.aiyaschoolpush.helper; + +import android.content.Context; +import android.graphics.Typeface; + +/** + * 统一 字体加载类 + * + * 第一次加载在MainActivity 中调用 + * (字体加载为相对耗时操作 所以做成单例) + */ +public class FontCustomHelper { + + private Typeface typeface; + private static final String FONT_URL = "fonts/icomoon.ttf"; + private static class Holder{ + private static FontCustomHelper helper = new FontCustomHelper(); + } + + public static FontCustomHelper getInstance(){ + return Holder.helper; + } + + + public void init(Context mContext){ + typeface = Typeface.createFromAsset(mContext.getAssets(), FONT_URL); + } + + public Typeface getTypeface(Context mContext) { + if (null==typeface) { + init(mContext); + } + return typeface; + } + + public void setTypeface(Typeface typeface) { + this.typeface = typeface; + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/LocalStorageHelper.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/LocalStorageHelper.java new file mode 100644 index 0000000..904f0ff --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/LocalStorageHelper.java @@ -0,0 +1,120 @@ +package com.example.nanchen.aiyaschoolpush.helper; + +import android.text.TextUtils; + +import java.io.File; +import java.io.IOException; + +/** + * 本地存储工具类 + * + * @author Frank + * @email frank@mondol.info + * @create_date 2015年4月7日 + */ +public class LocalStorageHelper { + private static final String FILE_TAG_IMAGE = "image"; + private static final String FILE_TAG_AUDIO = "audio"; + private static final String FILE_TAG_VIDEO = "video"; + static File mFilesDir; + static File mUsersDir; + + /** + * 创建一个空的临时文件,用后请删除 + * + * @param suffix + * 文件扩展名,传null则使用.tmp + * @return null则失败 + */ + public static File createTempFile(String suffix) { + return FileHelper.createTempFile(suffix); + } + + /** + * 创建班级圈视频 + * + * 返回文件路径,不需要请删除 + */ + public static File createGroupVideoFile(long groupId) { + return createUserTempFile("circle_" + groupId, FILE_TAG_VIDEO, ".mp4"); + } + + /** + * 创建一个空的讨论组图标文件 + */ + public static File createDiscussGroupIconFile(String jid) { + return createUserTempFile("group_" + jid, null, ".jpg"); + } + + /** + * 创建一个空的用于聊天的图片文件 + * + * @param userId + * 用户ID + */ + public static File createChatImageFile(long userId) { + return createUserTempFile("chat_" + userId, FILE_TAG_IMAGE, ".jpg"); + } + + /** + * 创建一个空的用于启动页广告图片文件 + */ + public static File createAdImageFile() { + return createUserTempFile("start_ad", FILE_TAG_IMAGE, ".jpg"); + } + /** + * 创建一个空的用于聊天的图片文件 + * + * @param jid + * 讨论组ID + * @return + */ + public static File createChatImageFile(String jid) { + return createUserTempFile("chat_" + jid, FILE_TAG_IMAGE, ".jpg"); + } + + /** + * 创建一个空的用于聊天的语音文件 + * + * @param userId + * @return + */ + public static File createChatAudioFile(long userId) { + return createUserTempFile("chat_" + userId, FILE_TAG_AUDIO, ".amr"); + } + + /** + * 创建一个空的用于聊天的语音文件 + * + * @param jid + * @return + */ + public static File createChatAudioFile(String jid) { + return createUserTempFile("chat_" + jid, FILE_TAG_AUDIO, ".amr"); + } + + private static File createUserTempFile(String uidOrJid, String fileTag, String suffix) { + if (mFilesDir == null) + mFilesDir = FileHelper.getFilesDir(); + if (mUsersDir == null) { + mUsersDir = new File(mFilesDir, "users"); + mUsersDir.mkdir(); + } + + String userDir = TextUtils.isEmpty(uidOrJid) ? "empty" : StringHelper.getMD5String(uidOrJid); + File fDir = new File(mUsersDir, userDir); + fDir.mkdir(); + + if (fileTag != null) { + fDir = new File(fDir, fileTag); + fDir.mkdir(); + } + + try { + return File.createTempFile(fileTag != null ? fileTag : "file", suffix, fDir); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/QiYuCloudServerHelper.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/QiYuCloudServerHelper.java new file mode 100644 index 0000000..52cb950 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/QiYuCloudServerHelper.java @@ -0,0 +1,225 @@ +package com.example.nanchen.aiyaschoolpush.helper; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import com.alibaba.fastjson.JSONArray; +import com.example.nanchen.aiyaschoolpush.R; +import com.qiyukf.nimlib.sdk.NIMClient; +import com.qiyukf.nimlib.sdk.msg.MsgService; +import com.qiyukf.nimlib.sdk.msg.constant.SessionTypeEnum; +import com.qiyukf.unicorn.api.ImageLoaderListener; +import com.qiyukf.unicorn.api.StatusBarNotificationConfig; +import com.qiyukf.unicorn.api.UICustomization; +import com.qiyukf.unicorn.api.Unicorn; +import com.qiyukf.unicorn.api.UnicornImageLoader; +import com.qiyukf.unicorn.api.YSFOptions; +import com.qiyukf.unicorn.api.YSFUserInfo; +import com.squareup.picasso.Picasso; +import com.squareup.picasso.RequestCreator; +import com.squareup.picasso.Target; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashSet; +import java.util.Set; + + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.helper + * @date 2016/11/04 15:47 + *

+ * 七鱼云客服相关帮助类 + */ + +public class QiYuCloudServerHelper { + + + private static Context mContext; + private static YSFOptions mOptins; + + private final static String APP_KEY = "2c170d44ffed6896868d56a13a09f63c"; + + + /** + * 初始化七鱼云客服 + */ + public static void initCloudServer(Application app) { + mContext = app; + Unicorn.init(app, APP_KEY, getServerOptions(), new PicassoImageLoader()); + } + + private static YSFOptions getServerOptions() { + if (mOptins == null) { + YSFOptions options = new YSFOptions(); + // 通知栏提醒 + options.statusBarNotificationConfig = new StatusBarNotificationConfig(); + options.statusBarNotificationConfig.notificationSmallIconId = R.mipmap.icon_notify; + // UI定制 头像版本更新,不再支持drawable://格式 + UICustomization customization = new UICustomization(); +// customization.msgBackgroundColor = mContext.getResources().getColor(R.color.main_bg_color); +// customization.tipsTextColor = mContext.getResources().getColor(R.color.gray3); +// customization.msgListViewDividerHeight = 40; +// customization.leftAvatar = "file://" + R.drawable.service; +// customization.tipsTextSize = 12; +// customization.msgItemBackgroundLeft = R.drawable.qiyu_message_item_selector; +// customization.msgItemBackgroundRight = R.drawable.qiyu_message_item_selector; +// customization.textMsgColorLeft = mContext.getResources().getColor(R.color.srs_text); +// customization.textMsgColorRight = mContext.getResources().getColor(R.color.srs_text); +// customization.titleBackgroundColor = mContext.getResources().getColor(R.color.main_bg_color1); +// customization.textMsgSize = 18; +// options.uiCustomization = customization; +// options.savePowerConfig = new SavePowerConfig();//省电策略 + mOptins = options; + } + return mOptins; + } + + + /** + * 设置客服消息提醒的跳转入口 + */ + private static void setNotifyActivity(Activity activity) { + mOptins.statusBarNotificationConfig.notificationEntrance = activity.getClass(); + } + + /** + * 启用通知 + * + * @param activity 通知跳转入口 + */ + public static void enableNotify(Activity activity) { + setNotifyActivity(activity); + NIMClient.getService(MsgService.class).setChattingAccount(MsgService.MSG_CHATTING_ACCOUNT_NONE, SessionTypeEnum.None); + } + + /** + * 禁用通知 + */ + public static void disableNotify() { + NIMClient.getService(MsgService.class).setChattingAccount(MsgService.MSG_CHATTING_ACCOUNT_ALL, SessionTypeEnum.None); + } + + /** + * 设置用户信息 + * + * @param login 当前用户是否处于登录状态 + */ + public static void setUserInfo(boolean login) { + Unicorn.setUserInfo(getCurrentUserInfo(login)); + } + + + /** + * 设置当前登录用户信息 + * + * @param login 当前用户是否处于登录状态 + * @return + */ + private static YSFUserInfo getCurrentUserInfo(boolean login) { + YSFUserInfo userInfo = new YSFUserInfo(); + userInfo.data = userInfoData(login).toString(); + if (login) { + userInfo.userId = DemoHelper.getInstance().getCurrentUserName(); + } + return userInfo; + } + + + private static JSONArray userInfoData(boolean login) { + String userName = DemoHelper.getInstance().getCurrentUserName(); + String platform = null; + JSONArray array = new JSONArray(); + if (login){ + array.add(userInfoDataItem("real_name", userName, false, -1, null, null)); + } + array.add(userInfoDataItem("app_version", platform + "-" + Environment2.getPackageVersionName(), false, 0, "应用版本", null)); + array.add(userInfoDataItem("system_version", android.os.Build.VERSION.RELEASE, false, 1, "系统版本", null)); + array.add(userInfoDataItem("device_manufacturer", Build.MANUFACTURER, false, 2, "机型", null)); + array.add(userInfoDataItem("device_model", Build.MODEL, false, 3, "模块", null)); + return array; + } + + private static JSONObject userInfoDataItem(String key, Object value, boolean hidden, int index, String label, String href) { + JSONObject item = null; + try { + item = new JSONObject(); + item.put("key", key); + item.put("value", value); + if (hidden) { + item.put("hidden", true); + } + if (index >= 0) { + item.put("index", index); + } + if (!TextUtils.isEmpty(label)) { + item.put("label", label); + } + if (!TextUtils.isEmpty(href)) { + item.put("href", href); + } + } catch (JSONException e) { + e.printStackTrace(); + } + return item; + } + + private static class PicassoImageLoader implements UnicornImageLoader { + + private final Set protectedFromGarbageCollectorTargets = new HashSet<>(); + + @Nullable + @Override + public Bitmap loadImageSync(String uri, int width, int height) { + return null; + } + + @Override + public void loadImage(final String uri, final int width, final int height, final ImageLoaderListener listener) { + final Target mTarget = new Target() { + @Override + public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { + if (listener != null) { + listener.onLoadComplete(bitmap); + protectedFromGarbageCollectorTargets.remove(this); + } + } + + @Override + public void onBitmapFailed(Drawable errorDrawable) { + if (listener != null) { + listener.onLoadFailed(null); + protectedFromGarbageCollectorTargets.remove(this); + } + } + + @Override + public void onPrepareLoad(Drawable placeHolderDrawable) { + + } + }; + ((Activity)mContext).runOnUiThread(new Runnable() { + @Override + public void run() { + RequestCreator requestCreator = Picasso.with(mContext).load(uri).config(Bitmap.Config.RGB_565); + if (width > 0 && height > 0) { + requestCreator = requestCreator.resize(width, height); + } + protectedFromGarbageCollectorTargets.add(mTarget); + requestCreator.into(mTarget); + } + }); + } + } + + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/StringHelper.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/StringHelper.java new file mode 100644 index 0000000..c8cbfc9 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/StringHelper.java @@ -0,0 +1,194 @@ +package com.example.nanchen.aiyaschoolpush.helper; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.regex.Pattern; +import java.util.zip.CRC32; + +/** + * 字符串相关的Helper方法 + * + * */ +public final class StringHelper { + public static final String EMPTY = ""; + private final static Pattern pattern_isEmail = Pattern.compile("^[a-z0-9]+[\\w\\-\\.]*@[a-z0-9\\-]+(?:\\.[a-z0-9\\-]+)+$", Pattern.CASE_INSENSITIVE); + private final static Pattern pattern_isMobile = Pattern.compile("^1[3-9][0-9]{9}$"); + private final static Pattern pattern_isPassword = Pattern.compile(".{6,30}"); + + public static boolean isNullOrEmpty(String str) { + return str == null || str.length() == 0; + } + + public static boolean isNullOrWhiteSpace(String str) { + return str == null || str.length() == 0 || str.trim().length() == 0; + } + + public static boolean isEmail(String str) { + return pattern_isEmail.matcher(str).matches(); + } + + /** + * 判断指定的字符串是否是手机号 + * */ + public static boolean isMobile(String str) { + return pattern_isMobile.matcher(str).matches(); + } + + /** + * 获取指定字符串中 {@code lastChar} 前面的部分 如果不包含{@code lastChar}则返回原{@code str} + * */ + public static String getLeftStringByLastChar(String str, char lastChar) { + if (str == null) + return null; + int index = str.lastIndexOf(lastChar); + if (index == -1) + return str; + + return str.substring(0, index); + } + + /** + * 获取指定字符串中 {@code lastChar} 后面的部分 如果不包含{@code lastChar}则返回原{@code str} + * */ + public static String getRightStringByLastChar(String str, char lastChar) { + if (str == null) + return null; + int index = str.lastIndexOf(lastChar); + if (index == -1) + return str; + + return str.substring(index + 1); + } + + public static String bytesToHexString(byte[] bytes) { + StringBuilder sbHex = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + if ((b & 0xFF) < 0x10) + sbHex.append("0"); + sbHex.append(Integer.toHexString(b & 0xFF)); + } + return sbHex.toString(); + } + + public static String getMD5String(String str) { + try { + return getMD5String(str, "utf-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return null; + } + } + + public static String getMD5String(String str, String charsetName) throws UnsupportedEncodingException { + try { + return getHashString("MD5", str, charsetName); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + throw new RuntimeException("当前环境不支持MD5算法!"); + } + } + + public static String getSHA1String(String str) { + try { + return getSHA1String(str, "utf-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return null; + } + } + + public static String getSHA1String(String str, String charsetName) throws UnsupportedEncodingException { + try { + return getHashString("SHA-1", str, charsetName); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + throw new RuntimeException("当前环境不支持SHA-1算法!"); + } + } + + public static String getCRC32String(String str) { + try { + return getCRC32String(str, "utf-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return null; + } + } + + public static String getCRC32String(String str, String charsetName) throws UnsupportedEncodingException { + try { + return getHashString("CRC32", str, charsetName); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + throw new RuntimeException("当前环境不支持CRC32算法!"); + } + } + + private static String getHashString(String algorithm, String str, String charsetName) throws UnsupportedEncodingException, NoSuchAlgorithmException { + if ("CRC32".equals(algorithm)) { + CRC32 crc = new CRC32(); + crc.update(str.getBytes(charsetName)); + long lVal = crc.getValue(); + return Long.toHexString(lVal).toLowerCase(); + } else { + byte[] bysHash = MessageDigest.getInstance(algorithm).digest(str.getBytes(charsetName)); + return bytesToHexString(bysHash); + } + } + + public static boolean isValidUserName(String str) { + final int len = str.length(); + boolean allNumber = true; + for (int i = 0; i < len; i++) { + char c = str.charAt(i); + if ('0' > c || c > '9') { + allNumber = false; + break; + } + } + return allNumber; + } + + public static boolean isValidPwd(String str) { + return pattern_isPassword.matcher(str).matches(); + } + + /** + * 判断字符串是否为空 + * + * + * @param str + * @return + * + * @author WLF + */ + public static boolean isEmpty(String str) { + return str == null || str.length() == 0 || str.equalsIgnoreCase("null"); + } + + /** + * 判断字符串不为空 + * + * + * @param str + * @return + * + * @author WLF + */ + public static boolean isNotEmpty(String str) { + return !isEmpty(str); + } + + /** + * + * + * @param str + * @return + * + * @author WLF + */ + public static String trim(String str) { + return str == null ? EMPTY : str.trim(); + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/CommunityEvent.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/CommunityEvent.java new file mode 100644 index 0000000..c991cc1 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/CommunityEvent.java @@ -0,0 +1,27 @@ +package com.example.nanchen.aiyaschoolpush.helper.event; + +import com.example.nanchen.aiyaschoolpush.model.info.InfoModel; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush + * @date 2016/11/23 13:46 + */ + +public class CommunityEvent { + private InfoModel mInfoModel; + + public CommunityEvent(InfoModel infoModel) { + mInfoModel = infoModel; + } + public CommunityEvent(){} + + public InfoModel getInfoModel() { + return mInfoModel; + } + + public void setInfoModel(InfoModel infoModel) { + mInfoModel = infoModel; + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/HomeworkEvent.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/HomeworkEvent.java new file mode 100644 index 0000000..5f2dcc5 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/HomeworkEvent.java @@ -0,0 +1,28 @@ +package com.example.nanchen.aiyaschoolpush.helper.event; + +import com.example.nanchen.aiyaschoolpush.model.info.InfoModel; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush + * @date 2016/11/22 13:47 + */ + +public class HomeworkEvent { + private InfoModel mInfoModel; + + public HomeworkEvent(InfoModel infoModel){ + this.mInfoModel = infoModel; + } + + public HomeworkEvent(){} + + public InfoModel getInfoModel() { + return mInfoModel; + } + + public void setInfoModel(InfoModel infoModel) { + mInfoModel = infoModel; + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/NetStateEvent.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/NetStateEvent.java new file mode 100644 index 0000000..4101959 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/NetStateEvent.java @@ -0,0 +1,13 @@ +package com.example.nanchen.aiyaschoolpush.helper.event; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.helper.event + * @date 2016/12/01 15:19 + */ + +public class NetStateEvent { + + public NetStateEvent(){} +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/NoticeEvent.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/NoticeEvent.java new file mode 100644 index 0000000..980f83c --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/NoticeEvent.java @@ -0,0 +1,41 @@ +package com.example.nanchen.aiyaschoolpush.helper.event; + +import com.example.nanchen.aiyaschoolpush.model.info.InfoModel; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush + * @date 2016/11/22 09:58 + */ + +public class NoticeEvent { + private InfoModel mInfoModel; + private int mCommentCount; + public NoticeEvent(InfoModel infoModel,int commentCount){ + this(infoModel); + this.mCommentCount = commentCount; + } + + public NoticeEvent(InfoModel infoModel){ + this.mInfoModel = infoModel; + } + + public NoticeEvent(){} + + public int getCommentCount() { + return mCommentCount; + } + + public void setCommentCount(int commentCount) { + mCommentCount = commentCount; + } + + public InfoModel getInfoModel() { + return mInfoModel; + } + + public void setInfoModel(InfoModel infoModel) { + mInfoModel = infoModel; + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/UpdateUserEvent.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/UpdateUserEvent.java new file mode 100644 index 0000000..a622576 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/UpdateUserEvent.java @@ -0,0 +1,14 @@ +package com.example.nanchen.aiyaschoolpush.helper.event; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.event + * @date 2016/11/29 11:58 + */ + +public class UpdateUserEvent { + public UpdateUserEvent(){ + + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/receiver/AvatarReceiver.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/receiver/AvatarReceiver.java new file mode 100644 index 0000000..4545ec8 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/receiver/AvatarReceiver.java @@ -0,0 +1,51 @@ +package com.example.nanchen.aiyaschoolpush.helper.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.receiver + * @date 2016/11/11 11:40 + * + * 头像改变的广播接收器,防止用户在个人信息更改图片后主页面头像未更新的问题 + */ + +public class AvatarReceiver extends BroadcastReceiver { + + private static final String TAG = "AvatarReceiver"; + + public static final String AVATAR_ACTION = "com.nanchen.android.AVATAR_ACTION"; + + + private AvatarCallback mAvatarCallback; + + public AvatarReceiver(AvatarCallback avatarCallback){ + mAvatarCallback = avatarCallback; + } + + public AvatarReceiver(){} + + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + Log.e(TAG,action); + // 如果是正确的action + if (AVATAR_ACTION.equals(action)){ + if (mAvatarCallback != null){ + mAvatarCallback.onAvatarChanged(); + } + } + } + + public interface AvatarCallback{ + /** + * 头像更改时调用 + */ + void onAvatarChanged(); + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/receiver/MiMessageReceiver.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/receiver/MiMessageReceiver.java new file mode 100644 index 0000000..cef3b6c --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/receiver/MiMessageReceiver.java @@ -0,0 +1,113 @@ +package com.example.nanchen.aiyaschoolpush.helper.receiver; + +import android.content.Context; +import android.text.TextUtils; + +import com.xiaomi.mipush.sdk.ErrorCode; +import com.xiaomi.mipush.sdk.MiPushClient; +import com.xiaomi.mipush.sdk.MiPushCommandMessage; +import com.xiaomi.mipush.sdk.MiPushMessage; +import com.xiaomi.mipush.sdk.PushMessageReceiver; + +import java.util.List; + +/** + * 小米推送消息接收器 + * + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.receiver + * @date 2016/10/31 11:59 + */ + +public class MiMessageReceiver extends PushMessageReceiver { + private String mRegId; + private long mResultCode = -1; + private String mReason; + private String mCommand; + private String mMessage; + private String mTopic; + private String mAlias; + private String mUserAccount; + private String mStartTime; + private String mEndTime; + @Override + public void onReceivePassThroughMessage(Context context, MiPushMessage message) { + + mMessage = message.getContent(); + if(!TextUtils.isEmpty(message.getTopic())) { + mTopic=message.getTopic(); + } else if(!TextUtils.isEmpty(message.getAlias())) { + mAlias=message.getAlias(); + } else if(!TextUtils.isEmpty(message.getUserAccount())) { + mUserAccount=message.getUserAccount(); + } + } + @Override + public void onNotificationMessageClicked(Context context, MiPushMessage message) { + mMessage = message.getContent(); + if(!TextUtils.isEmpty(message.getTopic())) { + mTopic=message.getTopic(); + } else if(!TextUtils.isEmpty(message.getAlias())) { + mAlias=message.getAlias(); + } else if(!TextUtils.isEmpty(message.getUserAccount())) { + mUserAccount=message.getUserAccount(); + } + } + @Override + public void onNotificationMessageArrived(Context context, MiPushMessage message) { + mMessage = message.getContent(); + if(!TextUtils.isEmpty(message.getTopic())) { + mTopic=message.getTopic(); + } else if(!TextUtils.isEmpty(message.getAlias())) { + mAlias=message.getAlias(); + } else if(!TextUtils.isEmpty(message.getUserAccount())) { + mUserAccount=message.getUserAccount(); + } + } + @Override + public void onCommandResult(Context context, MiPushCommandMessage message) { + String command = message.getCommand(); + List arguments = message.getCommandArguments(); + String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null); + String cmdArg2 = ((arguments != null && arguments.size() > 1) ? arguments.get(1) : null); + if (MiPushClient.COMMAND_REGISTER.equals(command)) { + if (message.getResultCode() == ErrorCode.SUCCESS) { + mRegId = cmdArg1; + } + } else if (MiPushClient.COMMAND_SET_ALIAS.equals(command)) { + if (message.getResultCode() == ErrorCode.SUCCESS) { + mAlias = cmdArg1; + } + } else if (MiPushClient.COMMAND_UNSET_ALIAS.equals(command)) { + if (message.getResultCode() == ErrorCode.SUCCESS) { + mAlias = cmdArg1; + } + } else if (MiPushClient.COMMAND_SUBSCRIBE_TOPIC.equals(command)) { + if (message.getResultCode() == ErrorCode.SUCCESS) { + mTopic = cmdArg1; + } + } else if (MiPushClient.COMMAND_UNSUBSCRIBE_TOPIC.equals(command)) { + if (message.getResultCode() == ErrorCode.SUCCESS) { + mTopic = cmdArg1; + } + } else if (MiPushClient.COMMAND_SET_ACCEPT_TIME.equals(command)) { + if (message.getResultCode() == ErrorCode.SUCCESS) { + mStartTime = cmdArg1; + mEndTime = cmdArg2; + } + } + } + @Override + public void onReceiveRegisterResult(Context context, MiPushCommandMessage message) { + String command = message.getCommand(); + List arguments = message.getCommandArguments(); + String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null); + String cmdArg2 = ((arguments != null && arguments.size() > 1) ? arguments.get(1) : null); + if (MiPushClient.COMMAND_REGISTER.equals(command)) { + if (message.getResultCode() == ErrorCode.SUCCESS) { + mRegId = cmdArg1; + } + } + } +} \ No newline at end of file diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/Constant.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/Constant.java new file mode 100644 index 0000000..dd975d4 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/Constant.java @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2016 Hyphenate Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.nanchen.aiyaschoolpush.im; + +import com.hyphenate.easeui.EaseConstant; + +public class Constant extends EaseConstant{ + public static final String NEW_FRIENDS_USERNAME = "item_new_friends"; + public static final String GROUP_USERNAME = "item_groups"; + public static final String CHAT_ROOM = "item_chatroom"; + public static final String ACCOUNT_REMOVED = "account_removed"; + public static final String ACCOUNT_CONFLICT = "conflict"; + public static final String ACCOUNT_FORBIDDEN = "user_forbidden"; + public static final String CHAT_ROBOT = "item_robots"; + public static final String MESSAGE_ATTR_ROBOT_MSGTYPE = "msgtype"; + public static final String ACTION_GROUP_CHANAGED = "action_group_changed"; + public static final String ACTION_CONTACT_CHANAGED = "action_contact_changed"; +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/DataSyncListener.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/DataSyncListener.java new file mode 100644 index 0000000..51e4b05 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/DataSyncListener.java @@ -0,0 +1,16 @@ +package com.example.nanchen.aiyaschoolpush.im; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.im + * @date 2016/10/28 08:52 + */ + +public interface DataSyncListener { + /** + * sync complete + * @param success true:data sync successful,false: failed to sync data + */ + void onSyncComplete(boolean success); +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/EmojiconExampleGroupData.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/EmojiconExampleGroupData.java new file mode 100644 index 0000000..fe2f121 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/EmojiconExampleGroupData.java @@ -0,0 +1,74 @@ +package com.example.nanchen.aiyaschoolpush.im; + +import com.example.nanchen.aiyaschoolpush.App; +import com.example.nanchen.aiyaschoolpush.R; +import com.hyphenate.easeui.domain.EaseEmojicon; +import com.hyphenate.easeui.domain.EaseEmojicon.Type; +import com.hyphenate.easeui.domain.EaseEmojiconGroupEntity; + +import java.util.Arrays; + +public class EmojiconExampleGroupData { + + private static int[] icons = new int[]{ + R.drawable.icon_002_cover, + R.drawable.icon_007_cover, + R.drawable.icon_010_cover, + R.drawable.icon_012_cover, + R.drawable.icon_013_cover, + R.drawable.icon_018_cover, + R.drawable.icon_019_cover, + R.drawable.icon_020_cover, + R.drawable.icon_021_cover, + R.drawable.icon_022_cover, + R.drawable.icon_024_cover, + R.drawable.icon_027_cover, + R.drawable.icon_029_cover, + R.drawable.icon_030_cover, + R.drawable.icon_035_cover, + R.drawable.icon_040_cover, + }; + + private static int[] bigIcons = new int[]{ + R.drawable.icon_002, + R.drawable.icon_007, + R.drawable.icon_010, + R.drawable.icon_012, + R.drawable.icon_013, + R.drawable.icon_018, + R.drawable.icon_019, + R.drawable.icon_020, + R.drawable.icon_021, + R.drawable.icon_022, + R.drawable.icon_024, + R.drawable.icon_027, + R.drawable.icon_029, + R.drawable.icon_030, + R.drawable.icon_035, + R.drawable.icon_040, + }; + + + private static final EaseEmojiconGroupEntity DATA = createData(); + + private static EaseEmojiconGroupEntity createData(){ + EaseEmojiconGroupEntity emojiconGroupEntity = new EaseEmojiconGroupEntity(); + EaseEmojicon[] datas = new EaseEmojicon[icons.length]; + for(int i = 0; i < icons.length; i++){ + datas[i] = new EaseEmojicon(icons[i], null, Type.BIG_EXPRESSION); + datas[i].setBigIcon(bigIcons[i]); + //you can replace this to any you want + datas[i].setName(App.getInstance().getApplicationContext().getString(R.string.emojicon_test_name)+ (i+1)); + datas[i].setIdentityCode("em"+ (1000+i+1)); + } + emojiconGroupEntity.setEmojiconList(Arrays.asList(datas)); + emojiconGroupEntity.setIcon(R.drawable.ee_2); + emojiconGroupEntity.setType(Type.BIG_EXPRESSION); + return emojiconGroupEntity; + } + + + public static EaseEmojiconGroupEntity getData(){ + return DATA; + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageCache.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageCache.java new file mode 100644 index 0000000..d32989f --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageCache.java @@ -0,0 +1,563 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.nanchen.aiyaschoolpush.im; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.os.Environment; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.util.LruCache; + +import java.io.File; +import java.lang.ref.SoftReference; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * This class memory caching of bitmaps in conjunction with the + * {@link ImageWorker} class and its subclasses. Use + * {@link ImageCache#getInstance(FragmentManager, ImageCacheParams)} + * to get an instance of this class, although usually a cache should be added + * directly to an {@link ImageWorker} by calling + * {@link ImageWorker#addImageCache(FragmentManager, ImageCacheParams)} + * . + */ +public class ImageCache { + private static final String TAG = "ImageCache"; + + // Default memory cache size in kilobytes + private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 5; // 5MB + + private static final int DEFAULT_COMPRESS_QUALITY = 70; + + // Constants to easily toggle various caches + private static final boolean DEFAULT_MEM_CACHE_ENABLED = true; + private static final boolean DEFAULT_INIT_DISK_CACHE_ON_CREATE = false; + + private LruCache mMemoryCache; + + private Set> mReusableBitmaps; + + /** + * Create a new ImageCache object using the specified parameters. This + * should not be called directly by other classes, instead use + * {@link ImageCache#getInstance(FragmentManager, ImageCacheParams)} + * to fetch an ImageCache instance. + * + * @param cacheParams + * The cache parameters to use to initialize the cache + */ + private ImageCache(ImageCacheParams cacheParams) { + init(cacheParams); + } + + /** + * Return an {@link ImageCache} instance. A {@link RetainFragment} is used + * to retain the ImageCache object across configuration changes such as a + * change in device orientation. + * + * @param fragmentManager + * The fragment manager to use when dealing with the retained + * fragment. + * @param cacheParams + * The cache parameters to use if the ImageCache needs + * instantiation. + * @return An existing retained ImageCache object or a new one if one did + * not exist + */ + public static ImageCache getInstance(FragmentManager fragmentManager, + ImageCacheParams cacheParams) { + + // Search for, or create an instance of the non-UI RetainFragment + final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager); + + // See if we already have an ImageCache stored in RetainFragment + ImageCache imageCache = (ImageCache) mRetainFragment.getObject(); + + // No existing ImageCache, create one and store it in RetainFragment + if (imageCache == null) { + imageCache = new ImageCache(cacheParams); + mRetainFragment.setObject(imageCache); + } + + return imageCache; + } + + /** + * Initialize the cache, providing all parameters. + * + * @param cacheParams + * The cache parameters to initialize the cache + */ + private void init(ImageCacheParams cacheParams) { + ImageCacheParams mCacheParams = cacheParams; + + // BEGIN_INCLUDE(init_memory_cache) + // Set up memory cache + if (mCacheParams.memoryCacheEnabled) { + + // If we're running on Honeycomb or newer, create a set of reusable + // bitmaps that can be + // populated into the inBitmap field of BitmapFactory.Options. Note + // that the set is + // of SoftReferences which will actually not be very effective due + // to the garbage + // collector being aggressive clearing Soft/WeakReferences. A better + // approach + // would be to use a strongly references bitmaps, however this would + // require some + // balancing of memory usage between this set and the bitmap + // LruCache. It would also + // require knowledge of the expected size of the bitmaps. From + // Honeycomb to JellyBean + // the size would need to be precise, from KitKat onward the size + // would just need to + // be the upper bound (due to changes in how inBitmap can re-use + // bitmaps). + if (Utils.hasHoneycomb()) { + mReusableBitmaps = Collections + .synchronizedSet(new HashSet>()); + } + + mMemoryCache = new LruCache( + mCacheParams.memCacheSize) { + + /** + * Notify the removed entry that is no longer being cached + */ + @Override + protected void entryRemoved(boolean evicted, String key, + BitmapDrawable oldValue, BitmapDrawable newValue) { + if (RecyclingBitmapDrawable.class.isInstance(oldValue)) { + // The removed entry is a recycling drawable, so notify + // it + // that it has been removed from the memory cache + ((RecyclingBitmapDrawable) oldValue).setIsCached(false); + } else { + // The removed entry is a standard BitmapDrawable + + if (Utils.hasHoneycomb()) { + // We're running on Honeycomb or later, so add the + // bitmap + // to a SoftReference set for possible use with + // inBitmap later + mReusableBitmaps.add(new SoftReference( + oldValue.getBitmap())); + } + } + } + + /** + * Measure item size in kilobytes rather than units which is + * more practical for a bitmap cache + */ + @Override + protected int sizeOf(String key, BitmapDrawable value) { + final int bitmapSize = getBitmapSize(value) / 1024; + return bitmapSize == 0 ? 1 : bitmapSize; + } + }; + } + + } + + /** + * Adds a bitmap to both memory and disk cache. + * + * @param data + * Unique identifier for the bitmap to store + * @param value + * The bitmap drawable to store + */ + public void addBitmapToCache(String data, BitmapDrawable value) { + // BEGIN_INCLUDE(add_bitmap_to_cache) + if (data == null || value == null) { + return; + } + + // Add to memory cache + if (mMemoryCache != null) { + if (RecyclingBitmapDrawable.class.isInstance(value)) { + // The removed entry is a recycling drawable, so notify it + // that it has been added into the memory cache + ((RecyclingBitmapDrawable) value).setIsCached(true); + } + mMemoryCache.put(data, value); + } + + } + + /** + * Get from memory cache. + * + * @param data + * Unique identifier for which item to get + * @return The bitmap drawable if found in cache, null otherwise + */ + public BitmapDrawable getBitmapFromMemCache(String data) { + // BEGIN_INCLUDE(get_bitmap_from_mem_cache) + BitmapDrawable memValue = null; + + if (mMemoryCache != null) { + memValue = mMemoryCache.get(data); + } + +// if (BuildConfig.DEBUG && memValue != null) { +// Log.d(TAG, "Memory cache hit"); +// } + + return memValue; + // END_INCLUDE(get_bitmap_from_mem_cache) + } + + /** + * @param options + * - BitmapFactory.Options with out* options populated + * @return Bitmap that case be used for inBitmap + */ + protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) { + // BEGIN_INCLUDE(get_bitmap_from_reusable_set) + Bitmap bitmap = null; + + if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) { + synchronized (mReusableBitmaps) { + final Iterator> iterator = mReusableBitmaps + .iterator(); + Bitmap item; + + while (iterator.hasNext()) { + item = iterator.next().get(); + + if (null != item && item.isMutable()) { + // Check to see it the item can be used for inBitmap + if (canUseForInBitmap(item, options)) { + bitmap = item; + + // Remove from reusable set so it can't be used + // again + iterator.remove(); + break; + } + } else { + // Remove from the set if the reference has been + // cleared. + iterator.remove(); + } + } + } + } + + return bitmap; + // END_INCLUDE(get_bitmap_from_reusable_set) + } + + /** + * Clears both the memory and disk cache associated with this ImageCache + * object. Note that this includes disk access so this should not be + * executed on the main/UI thread. + */ + public void clearCache() { + if (mMemoryCache != null) { + mMemoryCache.evictAll(); +// if (BuildConfig.DEBUG) { +// Log.d(TAG, "Memory cache cleared"); +// } + } + + } + + /** + * A holder class that contains cache parameters. + */ + public static class ImageCacheParams { + public int memCacheSize = DEFAULT_MEM_CACHE_SIZE; + public int compressQuality = DEFAULT_COMPRESS_QUALITY; + public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED; + public boolean initDiskCacheOnCreate = DEFAULT_INIT_DISK_CACHE_ON_CREATE; + + + /** + * Sets the memory cache size based on a percentage of the max available + * VM memory. Eg. setting percent to 0.2 would set the memory cache to + * one fifth of the available memory. Throws + * {@link IllegalArgumentException} if percent is < 0.01 or > .8. + * memCacheSize is stored in kilobytes instead of bytes as this will + * eventually be passed to construct a LruCache which takes an int in + * its constructor. + * + * This value should be chosen carefully based on a number of factors + * Refer to the corresponding Android Training class for more + * discussion: http://developer.android.com/training/displaying-bitmaps/ + * + * @param percent + * Percent of available app memory to use to size memory + * cache + */ + public void setMemCacheSizePercent(float percent) { + if (percent < 0.01f || percent > 0.8f) { + throw new IllegalArgumentException( + "setMemCacheSizePercent - percent must be " + + "between 0.01 and 0.8 (inclusive)"); + } + memCacheSize = Math.round(percent + * Runtime.getRuntime().maxMemory() / 1024); + } + } + + /** + * @param candidate + * - Bitmap to check + * @param targetOptions + * - Options that have the out* value populated + * @return true if candidate can be used for inBitmap re-use + * with targetOptions + */ + @TargetApi(19) + private static boolean canUseForInBitmap(Bitmap candidate, + BitmapFactory.Options targetOptions) { + // BEGIN_INCLUDE(can_use_for_inbitmap) + if (!Utils.hasKitKat()) { + // On earlier versions, the dimensions must match exactly and the + // inSampleSize must be 1 + return candidate.getWidth() == targetOptions.outWidth + && candidate.getHeight() == targetOptions.outHeight + && targetOptions.inSampleSize == 1; + } + + // From Android 4.4 (KitKat) onward we can re-use if the byte size of + // the new bitmap + // is smaller than the reusable bitmap candidate allocation byte count. + int width = targetOptions.outWidth / targetOptions.inSampleSize; + int height = targetOptions.outHeight / targetOptions.inSampleSize; + int byteCount = width * height + * getBytesPerPixel(candidate.getConfig()); + return byteCount <= candidate.getByteCount(); + // END_INCLUDE(can_use_for_inbitmap) + } + + /** + * Return the byte usage per pixel of a bitmap based on its configuration. + * + * @param config + * The bitmap configuration. + * @return The byte usage per pixel. + */ + private static int getBytesPerPixel(Config config) { + if (config == Config.ARGB_8888) { + return 4; + } else if (config == Config.RGB_565) { + return 2; + } else if (config == Config.ARGB_4444) { + return 2; + } else if (config == Config.ALPHA_8) { + return 1; + } + return 1; + } + + /** + * Get a usable cache directory (external if available, internal otherwise). + * + * @param context + * The context to use + * @param uniqueName + * A unique directory name to append to the cache dir + * @return The cache dir + */ + public static File getDiskCacheDir(Context context, String uniqueName) { + // Check if media is mounted or storage is built-in, if so, try and use + // external cache dir + // otherwise use internal cache dir + final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment + .getExternalStorageState()) || !isExternalStorageRemovable() ? getExternalCacheDir( + context).getPath() + : context.getCacheDir().getPath(); + + return new File(cachePath + File.separator + uniqueName); + } + + /** + * A hashing method that changes a string (like a URL) into a hash suitable + * for using as a disk filename. + */ + public static String hashKeyForDisk(String key) { + String cacheKey; + try { + final MessageDigest mDigest = MessageDigest.getInstance("MD5"); + mDigest.update(key.getBytes()); + cacheKey = bytesToHexString(mDigest.digest()); + } catch (NoSuchAlgorithmException e) { + cacheKey = String.valueOf(key.hashCode()); + } + return cacheKey; + } + + private static String bytesToHexString(byte[] bytes) { + // http://stackoverflow.com/questions/332079 + StringBuilder sb = new StringBuilder(); + for (byte aByte : bytes) { + String hex = Integer.toHexString(0xFF & aByte); + if (hex.length() == 1) { + sb.append('0'); + } + sb.append(hex); + } + return sb.toString(); + } + + /** + * Get the size in bytes of a bitmap in a BitmapDrawable. Note that from + * Android 4.4 (KitKat) onward this returns the allocated memory size of the + * bitmap which can be larger than the actual bitmap data byte count (in the + * case it was re-used). + * + * @param value + * @return size in bytes + */ + @TargetApi(19) + public static int getBitmapSize(BitmapDrawable value) { + Bitmap bitmap = value.getBitmap(); + + // From KitKat onward use getAllocationByteCount() as allocated bytes + // can potentially be + // larger than bitmap byte count. +// if (Utils.hasKitKat()) { +// return bitmap.getAllocationByteCount(); +// } + + if (Utils.hasHoneycombMR1()) { + return bitmap.getByteCount(); + } + + // Pre HC-MR1 + return bitmap.getRowBytes() * bitmap.getHeight(); + } + + /** + * Check if external storage is built-in or removable. + * + * @return True if external storage is removable (like an SD card), false + * otherwise. + */ + @TargetApi(VERSION_CODES.GINGERBREAD) + public static boolean isExternalStorageRemovable() { + if (Utils.hasGingerbread()) { + return Environment.isExternalStorageRemovable(); + } + return true; + } + + /** + * Get the external app cache directory. + * + * @param context + * The context to use + * @return The external cache dir + */ + @TargetApi(VERSION_CODES.FROYO) + public static File getExternalCacheDir(Context context) { + if (Utils.hasFroyo()) { + return context.getExternalCacheDir(); + } + + // Before Froyo we need to construct the external cache dir ourselves + final String cacheDir = "/Android/data/" + context.getPackageName() + + "/cache/"; + return new File(Environment.getExternalStorageDirectory().getPath() + + cacheDir); + } + + /** + * Locate an existing instance of this Fragment or if not found, create and + * add it using FragmentManager. + * + * @param fm + * The FragmentManager manager to use. + * @return The existing instance of the Fragment or the new instance if just + * created. + */ + private static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { + // BEGIN_INCLUDE(find_create_retain_fragment) + // Check to see if we have retained the worker fragment. + RetainFragment mRetainFragment = (RetainFragment) fm + .findFragmentByTag(TAG); + + // If not retained (or first time running), we need to create and add + // it. + if (mRetainFragment == null) { + mRetainFragment = new RetainFragment(); + fm.beginTransaction().add(mRetainFragment, TAG) + .commitAllowingStateLoss(); + } + + return mRetainFragment; + // END_INCLUDE(find_create_retain_fragment) + } + + /** + * A simple non-UI Fragment that stores a single Object and is retained over + * configuration changes. It will be used to retain the ImageCache object. + */ + public static class RetainFragment extends Fragment { + private Object mObject; + + /** + * Empty constructor as per the Fragment documentation + */ + public RetainFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Make sure this Fragment is retained over a configuration change + setRetainInstance(true); + } + + /** + * Store a single object in this Fragment. + * + * @param object + * The object to store + */ + public void setObject(Object object) { + mObject = object; + } + + /** + * Get the stored object. + * + * @return The stored object + */ + public Object getObject() { + return mObject; + } + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageResizer.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageResizer.java new file mode 100644 index 0000000..1eb9f1d --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageResizer.java @@ -0,0 +1,291 @@ +package com.example.nanchen.aiyaschoolpush.im; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.media.ThumbnailUtils; +import android.os.Build; +import android.provider.MediaStore.Video.Thumbnails; + +import java.io.FileDescriptor; + +public class ImageResizer extends ImageWorker { + private static final String TAG = "ImageResizer"; + protected int mImageWidth; + protected int mImageHeight; + + /** + * Initialize providing a single target image size (used for both width and + * height); + * + * @param context + * @param imageWidth + * @param imageHeight + */ + public ImageResizer(Context context, int imageWidth, int imageHeight) { + super(context); + setImageSize(imageWidth, imageHeight); + } + + /** + * Initialize providing a single target image size (used for both width and + * height); + * + * @param context + * @param imageSize + */ + public ImageResizer(Context context, int imageSize) { + super(context); + setImageSize(imageSize); + } + + /** + * Set the target image width and height. + * + * @param width + * @param height + */ + public void setImageSize(int width, int height) { + mImageWidth = width; + mImageHeight = height; + } + + /** + * Set the target image size (width and height will be the same). + * + * @param size + */ + public void setImageSize(int size) { + setImageSize(size, size); + } + + /** + * The main processing method. This happens in a background task. In this + * case we are just sampling down the bitmap and returning it from a + * resource. + * + * @param resId + * @return + */ + private Bitmap processBitmap(int resId) { +// if (BuildConfig.DEBUG) { +// Log.d(TAG, "processBitmap - " + resId); +// } + return decodeSampledBitmapFromResource(mResources, resId, mImageWidth, + mImageHeight, getImageCache()); + } + + @Override + protected Bitmap processBitmap(Object data) { + + String filePath=String.valueOf(data); + return ThumbnailUtils.createVideoThumbnail(filePath, Thumbnails.MICRO_KIND); + } + + /** + * Decode and sample down a bitmap from resources to the requested width and + * height. + * + * @param res + * The resources object containing the image data + * @param resId + * The resource id of the image data + * @param reqWidth + * The requested width of the resulting bitmap + * @param reqHeight + * The requested height of the resulting bitmap + * @param cache + * The ImageCache used to find candidate bitmaps for use with + * inBitmap + * @return A bitmap sampled down from the original with the same aspect + * ratio and dimensions that are equal to or greater than the + * requested width and height + */ + public static Bitmap decodeSampledBitmapFromResource(Resources res, + int resId, int reqWidth, int reqHeight, ImageCache cache) { + + // BEGIN_INCLUDE (read_bitmap_dimensions) + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeResource(res, resId, options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, + reqHeight); + // END_INCLUDE (read_bitmap_dimensions) + + // If we're running on Honeycomb or newer, try to use inBitmap + if (Utils.hasHoneycomb()) { + addInBitmapOptions(options, cache); + } + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + return BitmapFactory.decodeResource(res, resId, options); + } + + /** + * Decode and sample down a bitmap from a file to the requested width and + * height. + * + * @param filename + * The full path of the file to decode + * @param reqWidth + * The requested width of the resulting bitmap + * @param reqHeight + * The requested height of the resulting bitmap + * @param cache + * The ImageCache used to find candidate bitmaps for use with + * inBitmap + * @return A bitmap sampled down from the original with the same aspect + * ratio and dimensions that are equal to or greater than the + * requested width and height + */ + public static Bitmap decodeSampledBitmapFromFile(String filename, + int reqWidth, int reqHeight, ImageCache cache) { + + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(filename, options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, + reqHeight); + + // If we're running on Honeycomb or newer, try to use inBitmap + if (Utils.hasHoneycomb()) { + addInBitmapOptions(options, cache); + } + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + return BitmapFactory.decodeFile(filename, options); + } + + /** + * Decode and sample down a bitmap from a file input stream to the requested + * width and height. + * + * @param fileDescriptor + * The file descriptor to read from + * @param reqWidth + * The requested width of the resulting bitmap + * @param reqHeight + * The requested height of the resulting bitmap + * @param cache + * The ImageCache used to find candidate bitmaps for use with + * inBitmap + * @return A bitmap sampled down from the original with the same aspect + * ratio and dimensions that are equal to or greater than the + * requested width and height + */ + public static Bitmap decodeSampledBitmapFromDescriptor( + FileDescriptor fileDescriptor, int reqWidth, int reqHeight, + ImageCache cache) { + + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, + reqHeight); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + + // If we're running on Honeycomb or newer, try to use inBitmap + if (Utils.hasHoneycomb()) { + addInBitmapOptions(options, cache); + } + + return BitmapFactory + .decodeFileDescriptor(fileDescriptor, null, options); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + private static void addInBitmapOptions(BitmapFactory.Options options, + ImageCache cache) { + // BEGIN_INCLUDE(add_bitmap_options) + // inBitmap only works with mutable bitmaps so force the decoder to + // return mutable bitmaps. + options.inMutable = true; + + if (cache != null) { + // Try and find a bitmap to use for inBitmap + Bitmap inBitmap = cache.getBitmapFromReusableSet(options); + + if (inBitmap != null) { + options.inBitmap = inBitmap; + } + } + // END_INCLUDE(add_bitmap_options) + } + + /** + * Calculate an inSampleSize for use in a + * {@link BitmapFactory.Options} object when decoding + * bitmaps using the decode* methods from + * {@link BitmapFactory}. This implementation calculates + * the closest inSampleSize that is a power of 2 and will result in the + * final decoded bitmap having a width and height equal to or larger than + * the requested width and height. + * + * @param options + * An options object with out* params already populated (run + * through a decode* method with inJustDecodeBounds==true + * @param reqWidth + * The requested width of the resulting bitmap + * @param reqHeight + * The requested height of the resulting bitmap + * @return The value to be used for inSampleSize + */ + public static int calculateInSampleSize(BitmapFactory.Options options, + int reqWidth, int reqHeight) { + // BEGIN_INCLUDE (calculate_sample_size) + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and + // keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) > reqHeight + && (halfWidth / inSampleSize) > reqWidth) { + inSampleSize *= 2; + } + + // This offers some additional logic in case the image has a strange + // aspect ratio. For example, a panorama may have a much larger + // width than height. In these cases the total pixels might still + // end up being too large to fit comfortably in memory, so we should + // be more aggressive with sample down the image (=larger + // inSampleSize). + + long totalPixels = width * height / inSampleSize; + + // Anything more than 2x the requested pixels we'll sample down + // further + final long totalReqPixelsCap = reqWidth * reqHeight * 2; + + while (totalPixels > totalReqPixelsCap) { + inSampleSize *= 2; + totalPixels /= 2; + } + } + return inSampleSize; + // END_INCLUDE (calculate_sample_size) + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageWorker.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageWorker.java new file mode 100644 index 0000000..d340f3b --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageWorker.java @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.nanchen.aiyaschoolpush.im; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.os.AsyncTask; +import android.support.v4.app.FragmentManager; +import android.widget.ImageView; + +import com.example.nanchen.aiyaschoolpush.App; + +import java.lang.ref.WeakReference; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * This class wraps up completing some arbitrary long running work when loading a bitmap to an + * ImageView. It handles things like using a memory and disk cache, running the work in a background + * thread and setting a placeholder image. + */ +public abstract class ImageWorker { + private static final String TAG = "ImageWorker"; + private static final int FADE_IN_TIME = 200; + + private ImageCache mImageCache; + private Bitmap mLoadingBitmap; + private boolean mFadeInBitmap = true; + private boolean mExitTasksEarly = false; + protected boolean mPauseWork = false; + private final Object mPauseWorkLock = new Object(); + + protected Resources mResources; + + private static final int MESSAGE_CLEAR = 0; + private static final int MESSAGE_INIT_DISK_CACHE = 1; + private static final int MESSAGE_FLUSH = 2; + private static final int MESSAGE_CLOSE = 3; + + public static final Executor DUAL_THREAD_EXECUTOR = Executors + .newFixedThreadPool(2); + + protected ImageWorker(Context context) { + mResources = context.getResources(); + } + + /** + * Load an image specified by the data parameter into an ImageView (override + * {@link ImageWorker#processBitmap(Object)} to define the processing logic). A memory and + * disk cache will be used if an {@link ImageCache} has been added using + * {@link ImageWorker#addImageCache(FragmentManager, ImageCache.ImageCacheParams)}. If the + * image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask} + * will be created to asynchronously load the bitmap. + * + * @param data The URL of the image to download. + * @param imageView The ImageView to bind the downloaded image to. + */ + public void loadImage(Object data, ImageView imageView) { + if (data == null) { + return; + } + + BitmapDrawable value = null; + + if (mImageCache != null) { + value = mImageCache.getBitmapFromMemCache(String.valueOf(data)); + } + + if (value != null) { + // Bitmap found in memory cache + imageView.setImageDrawable(value); + } else if (cancelPotentialWork(data, imageView)) { + //BEGIN_INCLUDE(execute_background_task) + final BitmapWorkerTask task = new BitmapWorkerTask(data, imageView); + final AsyncDrawable asyncDrawable = + new AsyncDrawable(mResources, mLoadingBitmap, task); + imageView.setImageDrawable(asyncDrawable); + + // NOTE: This uses a custom version of AsyncTask that has been pulled from the + // framework and slightly modified. Refer to the docs at the top of the class + // for more info on what was changed. + task.executeOnExecutor(DUAL_THREAD_EXECUTOR); + //END_INCLUDE(execute_background_task) + } + } + + /** + * Set placeholder bitmap that shows when the the background thread is running. + * + * @param bitmap + */ + public void setLoadingImage(Bitmap bitmap) { + mLoadingBitmap = bitmap; + } + + /** + * Set placeholder bitmap that shows when the the background thread is running. + * + * @param resId + */ + public void setLoadingImage(int resId) { + mLoadingBitmap = BitmapFactory.decodeResource(mResources, resId); + } + + /** + * Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap + * caching. + * @param fragmentManager + * @param cacheParams The cache parameters to use for the image cache. + */ + public void addImageCache(FragmentManager fragmentManager, + ImageCache.ImageCacheParams cacheParams) { + ImageCache.ImageCacheParams mImageCacheParams = cacheParams; + mImageCache = ImageCache.getInstance(fragmentManager, mImageCacheParams); + new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE); + } + + + /** + * If set to true, the image will fade-in once it has been loaded by the background thread. + */ + public void setImageFadeIn(boolean fadeIn) { + mFadeInBitmap = fadeIn; + } + + public void setExitTasksEarly(boolean exitTasksEarly) { + mExitTasksEarly = exitTasksEarly; + setPauseWork(false); + } + + /** + * Subclasses should override this to define any processing or work that must happen to produce + * the final bitmap. This will be executed in a background thread and be long running. For + * example, you could resize a large bitmap here, or pull down an image from the network. + * + * @param data The data to identify which image to process, as provided by + * {@link ImageWorker#loadImage(Object, ImageView)} + * @return The processed bitmap + */ + protected abstract Bitmap processBitmap(Object data); + + /** + * @return The {@link ImageCache} object currently being used by this ImageWorker. + */ + protected ImageCache getImageCache() { + return mImageCache; + } + + /** + * Cancels any pending work attached to the provided ImageView. + * @param imageView + */ + public static void cancelWork(ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + if (bitmapWorkerTask != null) { + bitmapWorkerTask.cancel(true); +// if (BuildConfig.DEBUG) { +// final Object bitmapData = bitmapWorkerTask.mData; +// Log.d(TAG, "cancelWork - cancelled work for " + bitmapData); +// } + } + } + + /** + * Returns true if the current work has been canceled or if there was no work in + * progress on this image view. + * Returns false if the work in progress deals with the same data. The work is not + * stopped in that case. + */ + public static boolean cancelPotentialWork(Object data, ImageView imageView) { + //BEGIN_INCLUDE(cancel_potential_work) + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final Object bitmapData = bitmapWorkerTask.mData; + if (bitmapData == null || !bitmapData.equals(data)) { + bitmapWorkerTask.cancel(true); +// if (BuildConfig.DEBUG) { +// Log.d(TAG, "cancelPotentialWork - cancelled work for " + data); +// } + } else { + // The same work is already in progress. + return false; + } + } + return true; + //END_INCLUDE(cancel_potential_work) + } + + /** + * @param imageView Any imageView + * @return Retrieve the currently active work task (if any) associated with this imageView. + * null if there is no such task. + */ + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + /** + * The actual AsyncTask that will asynchronously process the image. + */ + private class BitmapWorkerTask extends AsyncTask { + private Object mData; + private final WeakReference imageViewReference; + + public BitmapWorkerTask(Object data, ImageView imageView) { + mData = data; + imageViewReference = new WeakReference(imageView); + } + + /** + * Background processing. + */ + @Override + protected BitmapDrawable doInBackground(Void... params) { + //BEGIN_INCLUDE(load_bitmap_in_background) +// if (BuildConfig.DEBUG) { +// Log.d(TAG, "doInBackground - starting work"); +// } + + final String dataString = String.valueOf(mData); + Bitmap bitmap = null; + BitmapDrawable drawable = null; + + // Wait here if work is paused and the task is not cancelled + synchronized (mPauseWorkLock) { + while (mPauseWork && !isCancelled()) { + try { + mPauseWorkLock.wait(); + } catch (InterruptedException e) {} + } + } + + + + // If the bitmap was not found in the cache and this task has not been cancelled by + // another thread and the ImageView that was originally bound to this task is still + // bound back to this task and our "exit early" flag is not set, then call the main + // process method (as implemented by a subclass) + if (bitmap == null && !isCancelled() && getAttachedImageView() != null + && !mExitTasksEarly) { + bitmap = processBitmap(mData); + } + + // If the bitmap was processed and the image cache is available, then add the processed + // bitmap to the cache for future use. Note we don't check if the task was cancelled + // here, if it was, and the thread is still running, we may as well add the processed + // bitmap to our cache as it might be used again in the future + if (bitmap != null) { + if (Utils.hasHoneycomb()) { + // Running on Honeycomb or newer, so wrap in a standard BitmapDrawable + drawable = new BitmapDrawable(mResources, bitmap); + } else { + // Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable + // which will recycle automagically + drawable = new RecyclingBitmapDrawable(mResources, bitmap); + } + + if (mImageCache != null) { + mImageCache.addBitmapToCache(dataString, drawable); + } + } + +// if (BuildConfig.DEBUG) { +// Log.d(TAG, "doInBackground - finished work"); +// } + + return drawable; + //END_INCLUDE(load_bitmap_in_background) + } + + /** + * Once the image is processed, associates it to the imageView + */ + @Override + protected void onPostExecute(BitmapDrawable value) { + //BEGIN_INCLUDE(complete_background_work) + // if cancel was called on this task or the "exit early" flag is set then we're done + if (isCancelled() || mExitTasksEarly) { + value = null; + } + + final ImageView imageView = getAttachedImageView(); + if (value != null && imageView != null) { +// if (BuildConfig.DEBUG) { +// Log.d(TAG, "onPostExecute - setting bitmap"); +// } + setImageDrawable(imageView, value); + } + //END_INCLUDE(complete_background_work) + } + + @Override + protected void onCancelled(BitmapDrawable value) { + super.onCancelled(value); + synchronized (mPauseWorkLock) { + mPauseWorkLock.notifyAll(); + } + } + + /** + * Returns the ImageView associated with this task as long as the ImageView's task still + * points to this task as well. Returns null otherwise. + */ + private ImageView getAttachedImageView() { + final ImageView imageView = imageViewReference.get(); + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (this == bitmapWorkerTask) { + return imageView; + } + + return null; + } + } + + /** + * A custom Drawable that will be attached to the imageView while the work is in progress. + * Contains a reference to the actual worker task, so that it can be stopped if a new binding is + * required, and makes sure that only the last started worker process can bind its result, + * independently of the finish order. + */ + private static class AsyncDrawable extends BitmapDrawable { + private final WeakReference bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = + new WeakReference(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } + + /** + * Called when the processing is complete and the final drawable should be + * set on the ImageView. + * + * @param imageView + * @param drawable + */ + private void setImageDrawable(ImageView imageView, Drawable drawable) { + if (mFadeInBitmap) { + // Transition drawable with a transparent drawable and the final drawable + final TransitionDrawable td = + new TransitionDrawable(new Drawable[] { + new ColorDrawable(App.getAppContext().getResources().getColor(android.R.color.transparent)), + drawable + }); + // Set background to loading bitmap + imageView.setBackgroundDrawable( + new BitmapDrawable(mResources, mLoadingBitmap)); + + imageView.setImageDrawable(td); + td.startTransition(FADE_IN_TIME); + } else { + imageView.setImageDrawable(drawable); + } + } + + /** + * Pause any ongoing background work. This can be used as a temporary + * measure to improve performance. For example background work could + * be paused when a ListView or GridView is being scrolled using a + * {@link android.widget.AbsListView.OnScrollListener} to keep + * scrolling smooth. + *

+ * If work is paused, be sure setPauseWork(false) is called again + * before your fragment or activity is destroyed (for example during + * {@link android.app.Activity#onPause()}), or there is a risk the + * background thread will never finish. + */ + public void setPauseWork(boolean pauseWork) { + synchronized (mPauseWorkLock) { + mPauseWork = pauseWork; + if (!mPauseWork) { + mPauseWorkLock.notifyAll(); + } + } + } + + protected class CacheAsyncTask extends AsyncTask { + + @Override + protected Void doInBackground(Object... params) { + switch ((Integer)params[0]) { + case MESSAGE_CLEAR: + clearCacheInternal(); + break; + + } + return null; + } + } + + + protected void clearCacheInternal() { + if (mImageCache != null) { + mImageCache.clearCache(); + } + } + + + public void clearCache() { + new CacheAsyncTask().execute(MESSAGE_CLEAR); + } + + public void flushCache() { + new CacheAsyncTask().execute(MESSAGE_FLUSH); + } + + public void closeCache() { + new CacheAsyncTask().execute(MESSAGE_CLOSE); + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/InviteMesageStatus.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/InviteMesageStatus.java new file mode 100644 index 0000000..77c3665 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/InviteMesageStatus.java @@ -0,0 +1,34 @@ +package com.example.nanchen.aiyaschoolpush.im; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.im + * @date 2016/10/28 09:05 + */ + +public enum InviteMesageStatus { + //==contact + /**being invited*/ + BEINVITEED, + /**being refused*/ + BEREFUSED, + /**remote user already agreed*/ + BEAGREED, + + //==group application + /**remote user apply to join*/ + BEAPPLYED, + /**you have agreed to join*/ + AGREED, + /**you refused the join request*/ + REFUSED, + + //==group invitation + /**received remote user's invitation**/ + GROUPINVITATION, + /**remote user accept your invitation**/ + GROUPINVITATION_ACCEPTED, + /**remote user declined your invitation**/ + GROUPINVITATION_DECLINED +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/InviteMessage.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/InviteMessage.java new file mode 100644 index 0000000..2fbd8e1 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/InviteMessage.java @@ -0,0 +1,102 @@ +/** + * Copyright (C) 2016 Hyphenate Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.nanchen.aiyaschoolpush.im; + +public class InviteMessage { + private String from; + private long time; + private String reason; + + private InviteMesageStatus status; + private String groupId; + private String groupName; + private String groupInviter; + + + private int id; + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public long getTime() { + return time; + } + + public void setTime(long time) { + this.time = time; + } + + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public InviteMesageStatus getStatus() { + return status; + } + + public void setStatus(InviteMesageStatus status) { + this.status = status; + } + + + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public void setGroupInviter(String inviter) { + groupInviter = inviter; + } + + public String getGroupInviter() { + return groupInviter; + } + + + + +} + + + diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ParseManager.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ParseManager.java new file mode 100644 index 0000000..e9539f9 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ParseManager.java @@ -0,0 +1,229 @@ +package com.example.nanchen.aiyaschoolpush.im; + +import android.content.Context; + +import com.example.nanchen.aiyaschoolpush.helper.DemoHelper; +import com.hyphenate.EMValueCallBack; +import com.hyphenate.chat.EMClient; +import com.hyphenate.easeui.domain.EaseUser; +import com.hyphenate.easeui.utils.EaseCommonUtils; +import com.hyphenate.util.EMLog; +import com.parse.FindCallback; +import com.parse.GetCallback; +import com.parse.Parse; +import com.parse.ParseException; +import com.parse.ParseFile; +import com.parse.ParseObject; +import com.parse.ParseQuery; +import com.parse.SaveCallback; + +import java.util.ArrayList; +import java.util.List; + +public class ParseManager { + + private static final String TAG = ParseManager.class.getSimpleName(); + + private static final String ParseAppID = "UUL8TxlHwKj7ZXEUr2brF3ydOxirCXdIj9LscvJs"; + private static final String ParseClientKey = "B1jH9bmxuYyTcpoFfpeVslhmLYsytWTxqYqKQhBJ"; + +// private static final String ParseAppID = "task"; +// private static final String ParseClientKey = "123456789"; + + private static final String CONFIG_TABLE_NAME = "hxuser"; + private static final String CONFIG_USERNAME = "username"; + private static final String CONFIG_NICK = "nickname"; + private static final String CONFIG_AVATAR = "avatar"; + + private static final String parseServer = "http://parse.easemob.com/parse/"; + + private static ParseManager instance = new ParseManager(); + + + private ParseManager() { + } + + public static ParseManager getInstance() { + return instance; + } + + public void onInit(Context context) { + Context appContext = context.getApplicationContext(); + Parse.enableLocalDatastore(appContext); +// Parse.initialize(context, ParseAppID, ParseClientKey); + Parse.initialize(new Parse.Configuration.Builder(appContext) + .applicationId(ParseAppID) + .server(parseServer) + .build()); + } + + public boolean updateParseNickName(final String nickname) { + String username = EMClient.getInstance().getCurrentUser(); + ParseQuery pQuery = ParseQuery.getQuery(CONFIG_TABLE_NAME); + pQuery.whereEqualTo(CONFIG_USERNAME, username); + ParseObject pUser = null; + try { + pUser = pQuery.getFirst(); + if (pUser == null) { + return false; + } + pUser.put(CONFIG_NICK, nickname); + pUser.save(); + return true; + } catch (ParseException e) { + if(e.getCode()==ParseException.OBJECT_NOT_FOUND){ + pUser = new ParseObject(CONFIG_TABLE_NAME); + pUser.put(CONFIG_USERNAME, username); + pUser.put(CONFIG_NICK, nickname); + try { + pUser.save(); + return true; + } catch (ParseException e1) { + e1.printStackTrace(); + EMLog.e(TAG, "parse error " + e1.getMessage()); + } + + } + e.printStackTrace(); + EMLog.e(TAG, "parse error " + e.getMessage()); + } catch(Exception e) { + EMLog.e(TAG, "updateParseNickName error"); + e.printStackTrace(); + } + return false; + } + + public void getContactInfos(List usernames, final EMValueCallBack> callback) { + ParseQuery pQuery = ParseQuery.getQuery(CONFIG_TABLE_NAME); + pQuery.whereContainedIn(CONFIG_USERNAME, usernames); + pQuery.findInBackground(new FindCallback() { + + @Override + public void done(List arg0, ParseException arg1) { + if (arg0 != null) { + List mList = new ArrayList(); + for (ParseObject pObject : arg0) { + EaseUser user = new EaseUser(pObject.getString(CONFIG_USERNAME)); + ParseFile parseFile = pObject.getParseFile(CONFIG_AVATAR); + if (parseFile != null) { + user.setAvatar(parseFile.getUrl()); + } + user.setNick(pObject.getString(CONFIG_NICK)); + EaseCommonUtils.setUserInitialLetter(user); + mList.add(user); + } + callback.onSuccess(mList); + } else { + callback.onError(arg1.getCode(), arg1.getMessage()); + } + } + }); + } + + + public void asyncGetCurrentUserInfo(final EMValueCallBack callback){ + final String username = EMClient.getInstance().getCurrentUser(); + asyncGetUserInfo(username, new EMValueCallBack() { + + @Override + public void onSuccess(EaseUser value) { + callback.onSuccess(value); + } + + @Override + public void onError(int error, String errorMsg) { + if (error == ParseException.OBJECT_NOT_FOUND) { + ParseObject pUser = new ParseObject(CONFIG_TABLE_NAME); + pUser.put(CONFIG_USERNAME, username); + pUser.saveInBackground(new SaveCallback() { + + @Override + public void done(ParseException arg0) { + if(arg0==null){ + callback.onSuccess(new EaseUser(username)); + } + } + }); + }else{ + callback.onError(error, errorMsg); + } + } + }); + } + + public void asyncGetUserInfo(final String username,final EMValueCallBack callback){ + ParseQuery pQuery = ParseQuery.getQuery(CONFIG_TABLE_NAME); + pQuery.whereEqualTo(CONFIG_USERNAME, username); + pQuery.getFirstInBackground(new GetCallback() { + + @Override + public void done(ParseObject pUser, ParseException e) { + if(pUser!=null){ + String nick = pUser.getString(CONFIG_NICK); + ParseFile pFile = pUser.getParseFile(CONFIG_AVATAR); + if(callback!=null){ + EaseUser user = DemoHelper.getInstance().getContactList().get(username); + if(user!=null){ + user.setNick(nick); + if (pFile != null && pFile.getUrl() != null) { + user.setAvatar(pFile.getUrl()); + } + }else{ + user = new EaseUser(username); + user.setNick(nick); + if (pFile != null && pFile.getUrl() != null) { + user.setAvatar(pFile.getUrl()); + } + } + callback.onSuccess(user); + } + }else{ + if(callback!=null){ + callback.onError(e.getCode(), e.getMessage()); + } + } + + } + }); + } + + public String uploadParseAvatar(byte[] data) { + String username = EMClient.getInstance().getCurrentUser(); + ParseQuery pQuery = ParseQuery.getQuery(CONFIG_TABLE_NAME); + pQuery.whereEqualTo(CONFIG_USERNAME, username); + ParseObject pUser = null; + try { + pUser = pQuery.getFirst(); + if (pUser == null) { + pUser = new ParseObject(CONFIG_TABLE_NAME); + pUser.put(CONFIG_USERNAME, username); + } + ParseFile pFile = new ParseFile(data); + pUser.put(CONFIG_AVATAR, pFile); + pUser.save(); + return pFile.getUrl(); + } catch (ParseException e) { + if (e.getCode() == ParseException.OBJECT_NOT_FOUND) { + try { + pUser = new ParseObject(CONFIG_TABLE_NAME); + pUser.put(CONFIG_USERNAME, username); + ParseFile pFile = new ParseFile(data); + pUser.put(CONFIG_AVATAR, pFile); + pUser.save(); + return pFile.getUrl(); + } catch (ParseException e1) { + e1.printStackTrace(); + EMLog.e(TAG, "parse error " + e1.getMessage()); + } + } else { + e.printStackTrace(); + EMLog.e(TAG, "parse error " + e.getMessage()); + } + } catch(Exception e) { + EMLog.e(TAG, "uploadParseAvatar error"); + e.printStackTrace(); + } + return null; + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/Permissions.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/Permissions.java new file mode 100644 index 0000000..d1fc366 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/Permissions.java @@ -0,0 +1,25 @@ +/** + * Copyright 2015 Anthony Restaino + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + either express or implied. See the License for the specific language governing + permissions and limitations under the License. + */ +package com.example.nanchen.aiyaschoolpush.im; + +/** + * Enum class to handle the different states + * of permissions since the PackageManager only + * has a granted and denied state. + */ +enum Permissions { + GRANTED, + NOT_FOUND, + DENIED +} \ No newline at end of file diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/PermissionsManager.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/PermissionsManager.java new file mode 100644 index 0000000..433df85 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/PermissionsManager.java @@ -0,0 +1,381 @@ +/** + * Copyright 2015 Anthony Restaino + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + either express or implied. See the License for the specific language governing + permissions and limitations under the License. + */ +package com.example.nanchen.aiyaschoolpush.im; + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.Fragment; +import android.util.Log; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * A class to help you manage your permissions simply. + */ +public class PermissionsManager { + + private static final String TAG = PermissionsManager.class.getSimpleName(); + + private final Set mPendingRequests = new HashSet(1); + private final Set mPermissions = new HashSet(1); + private final List> mPendingActions = new ArrayList>(1); + + private static PermissionsManager mInstance = null; + + public static PermissionsManager getInstance() { + if (mInstance == null) { + mInstance = new PermissionsManager(); + } + return mInstance; + } + + private PermissionsManager() { + initializePermissionsMap(); + } + + /** + * This method uses reflection to read all the permissions in the Manifest class. + * This is necessary because some permissions do not exist on older versions of Android, + * since they do not exist, they will be denied when you check whether you have permission + * which is problematic since a new permission is often added where there was no previous + * permission required. We initialize a Set of available permissions and check the set + * when checking if we have permission since we want to know when we are denied a permission + * because it doesn't exist yet. + */ + private synchronized void initializePermissionsMap() { + Field[] fields = Manifest.permission.class.getFields(); + for (Field field : fields) { + String name = null; + try { + name = (String) field.get(""); + } catch (IllegalAccessException e) { + Log.e(TAG, "Could not access field", e); + } + mPermissions.add(name); + } + } + + /** + * This method retrieves all the permissions declared in the application's manifest. + * It returns a non null array of permisions that can be declared. + * + * @param activity the Activity necessary to check what permissions we have. + * @return a non null array of permissions that are declared in the application manifest. + */ + @NonNull + private synchronized String[] getManifestPermissions(@NonNull final Activity activity) { + PackageInfo packageInfo = null; + List list = new ArrayList(1); + try { + Log.d(TAG, activity.getPackageName()); + packageInfo = activity.getPackageManager().getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "A problem occurred when retrieving permissions", e); + } + if (packageInfo != null) { + String[] permissions = packageInfo.requestedPermissions; + if (permissions != null) { + for (String perm : permissions) { + Log.d(TAG, "Manifest contained permission: " + perm); + list.add(perm); + } + } + } + return list.toArray(new String[list.size()]); + } + + /** + * This method adds the {@link PermissionsResultAction} to the current list + * of pending actions that will be completed when the permissions are + * received. The list of permissions passed to this method are registered + * in the PermissionsResultAction object so that it will be notified of changes + * made to these permissions. + * + * @param permissions the required permissions for the action to be executed. + * @param action the action to add to the current list of pending actions. + */ + private synchronized void addPendingAction(@NonNull String[] permissions, + @Nullable PermissionsResultAction action) { + if (action == null) { + return; + } + action.registerPermissions(permissions); + mPendingActions.add(new WeakReference(action)); + } + + /** + * This method removes a pending action from the list of pending actions. + * It is used for cases where the permission has already been granted, so + * you immediately wish to remove the pending action from the queue and + * execute the action. + * + * @param action the action to remove + */ + private synchronized void removePendingAction(@Nullable PermissionsResultAction action) { + for (Iterator> iterator = mPendingActions.iterator(); + iterator.hasNext(); ) { + WeakReference weakRef = iterator.next(); + if (weakRef.get() == action || weakRef.get() == null) { + iterator.remove(); + } + } + } + + /** + * This static method can be used to check whether or not you have a specific permission. + * It is basically a less verbose method of using {@link ActivityCompat#checkSelfPermission(Context, String)} + * and will simply return a boolean whether or not you have the permission. If you pass + * in a null Context object, it will return false as otherwise it cannot check the permission. + * However, the Activity parameter is nullable so that you can pass in a reference that you + * are not always sure will be valid or not (e.g. getActivity() from Fragment). + * + * @param context the Context necessary to check the permission + * @param permission the permission to check + * @return true if you have been granted the permission, false otherwise + */ + @SuppressWarnings("unused") + public synchronized boolean hasPermission(@Nullable Context context, @NonNull String permission) { + return context != null && (ActivityCompat.checkSelfPermission(context, permission) + == PackageManager.PERMISSION_GRANTED || !mPermissions.contains(permission)); + } + + /** + * This static method can be used to check whether or not you have several specific permissions. + * It is simpler than checking using {@link ActivityCompat#checkSelfPermission(Context, String)} + * for each permission and will simply return a boolean whether or not you have all the permissions. + * If you pass in a null Context object, it will return false as otherwise it cannot check the + * permission. However, the Activity parameter is nullable so that you can pass in a reference + * that you are not always sure will be valid or not (e.g. getActivity() from Fragment). + * + * @param context the Context necessary to check the permission + * @param permissions the permissions to check + * @return true if you have been granted all the permissions, false otherwise + */ + @SuppressWarnings("unused") + public synchronized boolean hasAllPermissions(@Nullable Context context, @NonNull String[] permissions) { + if (context == null) { + return false; + } + boolean hasAllPermissions = true; + for (String perm : permissions) { + hasAllPermissions &= hasPermission(context, perm); + } + return hasAllPermissions; + } + + /** + * This method will request all the permissions declared in your application manifest + * for the specified {@link PermissionsResultAction}. The purpose of this method is to enable + * all permissions to be requested at one shot. The PermissionsResultAction is used to notify + * you of the user allowing or denying each permission. The Activity and PermissionsResultAction + * parameters are both annotated Nullable, but this method will not work if the Activity + * is null. It is only annotated Nullable as a courtesy to prevent crashes in the case + * that you call this from a Fragment where {@link Fragment#getActivity()} could yield + * null. Additionally, you will not receive any notification of permissions being granted + * if you provide a null PermissionsResultAction. + * + * @param activity the Activity necessary to request and check permissions. + * @param action the PermissionsResultAction used to notify you of permissions being accepted. + */ + @SuppressWarnings("unused") + public synchronized void requestAllManifestPermissionsIfNecessary(final @Nullable Activity activity, + final @Nullable PermissionsResultAction action) { + if (activity == null) { + return; + } + String[] perms = getManifestPermissions(activity); + requestPermissionsIfNecessaryForResult(activity, perms, action); + } + + /** + * This method should be used to execute a {@link PermissionsResultAction} for the array + * of permissions passed to this method. This method will request the permissions if + * they need to be requested (i.e. we don't have permission yet) and will add the + * PermissionsResultAction to the queue to be notified of permissions being granted or + * denied. In the case of pre-Android Marshmallow, permissions will be granted immediately. + * The Activity variable is nullable, but if it is null, the method will fail to execute. + * This is only nullable as a courtesy for Fragments where getActivity() may yeild null + * if the Fragment is not currently added to its parent Activity. + * + * @param activity the activity necessary to request the permissions. + * @param permissions the list of permissions to request for the {@link PermissionsResultAction}. + * @param action the PermissionsResultAction to notify when the permissions are granted or denied. + */ + @SuppressWarnings("unused") + public synchronized void requestPermissionsIfNecessaryForResult(@Nullable Activity activity, + @NonNull String[] permissions, + @Nullable PermissionsResultAction action) { + if (activity == null) { + return; + } + addPendingAction(permissions, action); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + doPermissionWorkBeforeAndroidM(activity, permissions, action); + } else { + List permList = getPermissionsListToRequest(activity, permissions, action); + if (permList.isEmpty()) { + //if there is no permission to request, there is no reason to keep the action int the list + removePendingAction(action); + } else { + String[] permsToRequest = permList.toArray(new String[permList.size()]); + mPendingRequests.addAll(permList); + ActivityCompat.requestPermissions(activity, permsToRequest, 1); + } + } + } + + /** + * This method should be used to execute a {@link PermissionsResultAction} for the array + * of permissions passed to this method. This method will request the permissions if + * they need to be requested (i.e. we don't have permission yet) and will add the + * PermissionsResultAction to the queue to be notified of permissions being granted or + * denied. In the case of pre-Android Marshmallow, permissions will be granted immediately. + * The Fragment variable is used, but if {@link Fragment#getActivity()} returns null, this method + * will fail to work as the activity reference is necessary to check for permissions. + * + * @param fragment the fragment necessary to request the permissions. + * @param permissions the list of permissions to request for the {@link PermissionsResultAction}. + * @param action the PermissionsResultAction to notify when the permissions are granted or denied. + */ + @SuppressWarnings("unused") + public synchronized void requestPermissionsIfNecessaryForResult(@NonNull Fragment fragment, + @NonNull String[] permissions, + @Nullable PermissionsResultAction action) { + Activity activity = fragment.getActivity(); + if (activity == null) { + return; + } + addPendingAction(permissions, action); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + doPermissionWorkBeforeAndroidM(activity, permissions, action); + } else { + List permList = getPermissionsListToRequest(activity, permissions, action); + if (permList.isEmpty()) { + //if there is no permission to request, there is no reason to keep the action int the list + removePendingAction(action); + } else { + String[] permsToRequest = permList.toArray(new String[permList.size()]); + mPendingRequests.addAll(permList); + fragment.requestPermissions(permsToRequest, 1); + } + } + } + + /** + * This method notifies the PermissionsManager that the permissions have change. If you are making + * the permissions requests using an Activity, then this method should be called from the + * Activity callback onRequestPermissionsResult() with the variables passed to that method. If + * you are passing a Fragment to make the permissions request, then you should call this in + * the {@link Fragment#onRequestPermissionsResult(int, String[], int[])} method. + * It will notify all the pending PermissionsResultAction objects currently + * in the queue, and will remove the permissions request from the list of pending requests. + * + * @param permissions the permissions that have changed. + * @param results the values for each permission. + */ + @SuppressWarnings("unused") + public synchronized void notifyPermissionsChange(@NonNull String[] permissions, @NonNull int[] results) { + int size = permissions.length; + if (results.length < size) { + size = results.length; + } + Iterator> iterator = mPendingActions.iterator(); + while (iterator.hasNext()) { + PermissionsResultAction action = iterator.next().get(); + for (int n = 0; n < size; n++) { + if (action == null || action.onResult(permissions[n], results[n])) { + iterator.remove(); + break; + } + } + } + for (int n = 0; n < size; n++) { + mPendingRequests.remove(permissions[n]); + } + } + + /** + * When request permissions on devices before Android M (Android 6.0, API Level 23) + * Do the granted or denied work directly according to the permission status + * + * @param activity the activity to check permissions + * @param permissions the permissions names + * @param action the callback work object, containing what we what to do after + * permission check + */ + private void doPermissionWorkBeforeAndroidM(@NonNull Activity activity, + @NonNull String[] permissions, + @Nullable PermissionsResultAction action) { + for (String perm : permissions) { + if (action != null) { + if (!mPermissions.contains(perm)) { + action.onResult(perm, Permissions.NOT_FOUND); + } else if (ActivityCompat.checkSelfPermission(activity, perm) + != PackageManager.PERMISSION_GRANTED) { + action.onResult(perm, Permissions.DENIED); + } else { + action.onResult(perm, Permissions.GRANTED); + } + } + } + } + + /** + * Filter the permissions list: + * If a permission is not granted, add it to the result list + * if a permission is granted, do the granted work, do not add it to the result list + * + * @param activity the activity to check permissions + * @param permissions all the permissions names + * @param action the callback work object, containing what we what to do after + * permission check + * @return a list of permissions names that are not granted yet + */ + @NonNull + private List getPermissionsListToRequest(@NonNull Activity activity, + @NonNull String[] permissions, + @Nullable PermissionsResultAction action) { + List permList = new ArrayList(permissions.length); + for (String perm : permissions) { + if (!mPermissions.contains(perm)) { + if (action != null) { + action.onResult(perm, Permissions.NOT_FOUND); + } + } else if (ActivityCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED) { + if (!mPendingRequests.contains(perm)) { + permList.add(perm); + } + } else { + if (action != null) { + action.onResult(perm, Permissions.GRANTED); + } + } + } + return permList; + } + +} \ No newline at end of file diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/PermissionsResultAction.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/PermissionsResultAction.java new file mode 100644 index 0000000..9028f26 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/PermissionsResultAction.java @@ -0,0 +1,181 @@ +/** + * Copyright 2015 Anthony Restaino + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + either express or implied. See the License for the specific language governing + permissions and limitations under the License. + */ +package com.example.nanchen.aiyaschoolpush.im; + +import android.content.pm.PackageManager; +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.CallSuper; +import android.support.annotation.NonNull; +import android.util.Log; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * This abstract class should be used to create an if/else action that the PermissionsManager + * can execute when the permissions you request are granted or denied. Simple use involves + * creating an anonymous instance of it and passing that instance to the + * requestPermissionsIfNecessaryForResult method. The result will be sent back to you as + * either onGranted (all permissions have been granted), or onDenied (a required permission + * has been denied). Ideally you put your functionality in the onGranted method and notify + * the user what won't work in the onDenied method. + */ +public abstract class PermissionsResultAction { + + private static final String TAG = PermissionsResultAction.class.getSimpleName(); + private final Set mPermissions = new HashSet(1); + private Looper mLooper = Looper.getMainLooper(); + + /** + * Default Constructor + */ + public PermissionsResultAction() {} + + /** + * Alternate Constructor. Pass the looper you wish the PermissionsResultAction + * callbacks to be executed on if it is not the current Looper. For instance, + * if you are making a permissions request from a background thread but wish the + * callback to be on the UI thread, use this constructor to specify the UI Looper. + * + * @param looper the looper that the callbacks will be called using. + */ + @SuppressWarnings("unused") + public PermissionsResultAction(@NonNull Looper looper) {mLooper = looper;} + + /** + * This method is called when ALL permissions that have been + * requested have been granted by the user. In this method + * you should put all your permissions sensitive code that can + * only be executed with the required permissions. + */ + public abstract void onGranted(); + + /** + * This method is called when a permission has been denied by + * the user. It provides you with the permission that was denied + * and will be executed on the Looper you pass to the constructor + * of this class, or the Looper that this object was created on. + * + * @param permission the permission that was denied. + */ + public abstract void onDenied(String permission); + + /** + * This method is used to determine if a permission not + * being present on the current Android platform should + * affect whether the PermissionsResultAction should continue + * listening for events. By default, it returns true and will + * simply ignore the permission that did not exist. Usually this will + * work fine since most new permissions are introduced to + * restrict what was previously allowed without permission. + * If that is not the case for your particular permission you + * request, override this method and return false to result in the + * Action being denied. + * + * @param permission the permission that doesn't exist on this + * Android version + * @return return true if the PermissionsResultAction should + * ignore the lack of the permission and proceed with exection + * or false if the PermissionsResultAction should treat the + * absence of the permission on the API level as a denial. + */ + @SuppressWarnings({"WeakerAccess", "SameReturnValue"}) + public synchronized boolean shouldIgnorePermissionNotFound(String permission) { + Log.d(TAG, "Permission not found: " + permission); + return true; + } + + @SuppressWarnings("WeakerAccess") + @CallSuper + protected synchronized final boolean onResult(final @NonNull String permission, int result) { + if (result == PackageManager.PERMISSION_GRANTED) { + return onResult(permission, Permissions.GRANTED); + } else { + return onResult(permission, Permissions.DENIED); + } + + } + + /** + * This method is called when a particular permission has changed. + * This method will be called for all permissions, so this method determines + * if the permission affects the state or not and whether it can proceed with + * calling onGranted or if onDenied should be called. + * + * @param permission the permission that changed. + * @param result the result for that permission. + * @return this method returns true if its primary action has been completed + * and it should be removed from the data structure holding a reference to it. + */ + @SuppressWarnings("WeakerAccess") + @CallSuper + protected synchronized final boolean onResult(final @NonNull String permission, Permissions result) { + mPermissions.remove(permission); + if (result == Permissions.GRANTED) { + if (mPermissions.isEmpty()) { + new Handler(mLooper).post(new Runnable() { + @Override + public void run() { + onGranted(); + } + }); + return true; + } + } else if (result == Permissions.DENIED) { + new Handler(mLooper).post(new Runnable() { + @Override + public void run() { + onDenied(permission); + } + }); + return true; + } else if (result == Permissions.NOT_FOUND) { + if (shouldIgnorePermissionNotFound(permission)) { + if (mPermissions.isEmpty()) { + new Handler(mLooper).post(new Runnable() { + @Override + public void run() { + onGranted(); + } + }); + return true; + } + } else { + new Handler(mLooper).post(new Runnable() { + @Override + public void run() { + onDenied(permission); + } + }); + return true; + } + } + return false; + } + + /** + * This method registers the PermissionsResultAction object for the specified permissions + * so that it will know which permissions to look for changes to. The PermissionsResultAction + * will then know to look out for changes to these permissions. + * + * @param perms the permissions to listen for + */ + @SuppressWarnings("WeakerAccess") + @CallSuper + protected synchronized final void registerPermissions(@NonNull String[] perms) { + Collections.addAll(mPermissions, perms); + } +} \ No newline at end of file diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/PreferenceManager.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/PreferenceManager.java new file mode 100644 index 0000000..a5da8c6 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/PreferenceManager.java @@ -0,0 +1,266 @@ +/** + * Copyright (C) 2016 Hyphenate Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.nanchen.aiyaschoolpush.im; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; + +public class PreferenceManager { + /** + * name of preference + */ + public static final String PREFERENCE_NAME = "saveInfo"; + private static SharedPreferences mSharedPreferences; + private static PreferenceManager mPreferencemManager; + private static SharedPreferences.Editor editor; + + private String SHARED_KEY_SETTING_NOTIFICATION = "shared_key_setting_notification"; + private String SHARED_KEY_SETTING_SOUND = "shared_key_setting_sound"; + private String SHARED_KEY_SETTING_VIBRATE = "shared_key_setting_vibrate"; + private String SHARED_KEY_SETTING_SPEAKER = "shared_key_setting_speaker"; + + private static String SHARED_KEY_SETTING_CHATROOM_OWNER_LEAVE = "shared_key_setting_chatroom_owner_leave"; + private static String SHARED_KEY_SETTING_DELETE_MESSAGES_WHEN_EXIT_GROUP = "shared_key_setting_delete_messages_when_exit_group"; + private static String SHARED_KEY_SETTING_AUTO_ACCEPT_GROUP_INVITATION = "shared_key_setting_auto_accept_group_invitation"; + private static String SHARED_KEY_SETTING_ADAPTIVE_VIDEO_ENCODE = "shared_key_setting_adaptive_video_encode"; + private static String SHARED_KEY_SETTING_OFFLINE_PUSH_CALL = "shared_key_setting_offline_push_call"; + + private static String SHARED_KEY_SETTING_GROUPS_SYNCED = "SHARED_KEY_SETTING_GROUPS_SYNCED"; + private static String SHARED_KEY_SETTING_CONTACT_SYNCED = "SHARED_KEY_SETTING_CONTACT_SYNCED"; + private static String SHARED_KEY_SETTING_BALCKLIST_SYNCED = "SHARED_KEY_SETTING_BALCKLIST_SYNCED"; + + private static String SHARED_KEY_CURRENTUSER_USERNAME = "SHARED_KEY_CURRENTUSER_USERNAME"; + private static String SHARED_KEY_CURRENTUSER_NICK = "SHARED_KEY_CURRENTUSER_NICK"; + private static String SHARED_KEY_CURRENTUSER_AVATAR = "SHARED_KEY_CURRENTUSER_AVATAR"; + + private static String SHARED_KEY_REST_SERVER = "SHARED_KEY_REST_SERVER"; + private static String SHARED_KEY_IM_SERVER = "SHARED_KEY_IM_SERVER"; + private static String SHARED_KEY_ENABLE_CUSTOM_SERVER = "SHARED_KEY_ENABLE_CUSTOM_SERVER"; + private static String SHARED_KEY_ENABLE_CUSTOM_APPKEY = "SHARED_KEY_ENABLE_CUSTOM_APPKEY"; + private static String SHARED_KEY_CUSTOM_APPKEY = "SHARED_KEY_CUSTOM_APPKEY"; + + @SuppressLint("CommitPrefEdits") + private PreferenceManager(Context cxt) { + mSharedPreferences = cxt.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + editor = mSharedPreferences.edit(); + } + + public static synchronized void init(Context cxt){ + if(mPreferencemManager == null){ + mPreferencemManager = new PreferenceManager(cxt); + } + } + + /** + * get instance of PreferenceManager + * + * @param + * @return + */ + public synchronized static PreferenceManager getInstance() { + if (mPreferencemManager == null) { + throw new RuntimeException("please init first!"); + } + + return mPreferencemManager; + } + + public void setSettingMsgNotification(boolean paramBoolean) { + editor.putBoolean(SHARED_KEY_SETTING_NOTIFICATION, paramBoolean); + editor.apply(); + } + + public boolean getSettingMsgNotification() { + return mSharedPreferences.getBoolean(SHARED_KEY_SETTING_NOTIFICATION, true); + } + + public void setSettingMsgSound(boolean paramBoolean) { + editor.putBoolean(SHARED_KEY_SETTING_SOUND, paramBoolean); + editor.apply(); + } + + public boolean getSettingMsgSound() { + + return mSharedPreferences.getBoolean(SHARED_KEY_SETTING_SOUND, true); + } + + public void setSettingMsgVibrate(boolean paramBoolean) { + editor.putBoolean(SHARED_KEY_SETTING_VIBRATE, paramBoolean); + editor.apply(); + } + + public boolean getSettingMsgVibrate() { + return mSharedPreferences.getBoolean(SHARED_KEY_SETTING_VIBRATE, true); + } + + public void setSettingMsgSpeaker(boolean paramBoolean) { + editor.putBoolean(SHARED_KEY_SETTING_SPEAKER, paramBoolean); + editor.apply(); + } + + public boolean getSettingMsgSpeaker() { + return mSharedPreferences.getBoolean(SHARED_KEY_SETTING_SPEAKER, true); + } + + public void setSettingAllowChatroomOwnerLeave(boolean value) { + editor.putBoolean(SHARED_KEY_SETTING_CHATROOM_OWNER_LEAVE, value); + editor.apply(); + } + + public boolean getSettingAllowChatroomOwnerLeave() { + return mSharedPreferences.getBoolean(SHARED_KEY_SETTING_CHATROOM_OWNER_LEAVE, true); + } + + public void setDeleteMessagesAsExitGroup(boolean value){ + editor.putBoolean(SHARED_KEY_SETTING_DELETE_MESSAGES_WHEN_EXIT_GROUP, value); + editor.apply(); + } + + public boolean isDeleteMessagesAsExitGroup() { + return mSharedPreferences.getBoolean(SHARED_KEY_SETTING_DELETE_MESSAGES_WHEN_EXIT_GROUP, true); + } + + public void setAutoAcceptGroupInvitation(boolean value) { + editor.putBoolean(SHARED_KEY_SETTING_AUTO_ACCEPT_GROUP_INVITATION, value); + editor.commit(); + } + + public boolean isAutoAcceptGroupInvitation() { + return mSharedPreferences.getBoolean(SHARED_KEY_SETTING_AUTO_ACCEPT_GROUP_INVITATION, true); + } + + public void setAdaptiveVideoEncode(boolean value) { + editor.putBoolean(SHARED_KEY_SETTING_ADAPTIVE_VIDEO_ENCODE, value); + editor.apply(); + } + + public boolean isAdaptiveVideoEncode() { + return mSharedPreferences.getBoolean(SHARED_KEY_SETTING_ADAPTIVE_VIDEO_ENCODE, false); + } + + public void setPushCall(boolean value) { + editor.putBoolean(SHARED_KEY_SETTING_OFFLINE_PUSH_CALL, value); + editor.apply(); + } + + public boolean isPushCall() { + return mSharedPreferences.getBoolean(SHARED_KEY_SETTING_OFFLINE_PUSH_CALL, false); + } + + public void setGroupsSynced(boolean synced){ + editor.putBoolean(SHARED_KEY_SETTING_GROUPS_SYNCED, synced); + editor.apply(); + } + + public boolean isGroupsSynced(){ + return mSharedPreferences.getBoolean(SHARED_KEY_SETTING_GROUPS_SYNCED, false); + } + + public void setContactSynced(boolean synced){ + editor.putBoolean(SHARED_KEY_SETTING_CONTACT_SYNCED, synced); + editor.apply(); + } + + public boolean isContactSynced(){ + return mSharedPreferences.getBoolean(SHARED_KEY_SETTING_CONTACT_SYNCED, false); + } + + public void setBlacklistSynced(boolean synced){ + editor.putBoolean(SHARED_KEY_SETTING_BALCKLIST_SYNCED, synced); + editor.apply(); + } + + public boolean isBacklistSynced(){ + return mSharedPreferences.getBoolean(SHARED_KEY_SETTING_BALCKLIST_SYNCED, false); + } + + public void setCurrentUserNick(String nick) { + editor.putString(SHARED_KEY_CURRENTUSER_NICK, nick); + editor.apply(); + } + + public void setCurrentUserAvatar(String avatar) { + editor.putString(SHARED_KEY_CURRENTUSER_AVATAR, avatar); + editor.apply(); + } + + public String getCurrentUserNick() { + return mSharedPreferences.getString(SHARED_KEY_CURRENTUSER_NICK, null); + } + + public String getCurrentUserAvatar() { + return mSharedPreferences.getString(SHARED_KEY_CURRENTUSER_AVATAR, null); + } + + public void setCurrentUserName(String username){ + editor.putString(SHARED_KEY_CURRENTUSER_USERNAME, username); + editor.apply(); + } + + public String getCurrentUsername(){ + return mSharedPreferences.getString(SHARED_KEY_CURRENTUSER_USERNAME, null); + } + + public void setRestServer(String restServer){ + editor.putString(SHARED_KEY_REST_SERVER, restServer).commit(); + editor.commit(); + } + + public String getRestServer(){ + return mSharedPreferences.getString(SHARED_KEY_REST_SERVER, null); + } + + public void setIMServer(String imServer){ + editor.putString(SHARED_KEY_IM_SERVER, imServer); + editor.commit(); + } + + public String getIMServer(){ + return mSharedPreferences.getString(SHARED_KEY_IM_SERVER, null); + } + + public void enableCustomServer(boolean enable){ + editor.putBoolean(SHARED_KEY_ENABLE_CUSTOM_SERVER, enable); + editor.apply(); + } + + public boolean isCustomServerEnable(){ + return mSharedPreferences.getBoolean(SHARED_KEY_ENABLE_CUSTOM_SERVER, false); + } + + public void enableCustomAppkey(boolean enable) { + editor.putBoolean(SHARED_KEY_ENABLE_CUSTOM_APPKEY, enable); + editor.apply(); + } + + public boolean isCustomAppkeyEnabled() { + return mSharedPreferences.getBoolean(SHARED_KEY_ENABLE_CUSTOM_APPKEY, false); + } + + public String getCustomAppkey() { + return mSharedPreferences.getString(SHARED_KEY_CUSTOM_APPKEY, ""); + } + + public void setCustomAppkey(String appkey) { + editor.putString(SHARED_KEY_CUSTOM_APPKEY, appkey); + editor.apply(); + } + + public void removeCurrentUserInfo() { + editor.remove(SHARED_KEY_CURRENTUSER_NICK); + editor.remove(SHARED_KEY_CURRENTUSER_AVATAR); + editor.apply(); + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/RecyclingBitmapDrawable.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/RecyclingBitmapDrawable.java new file mode 100644 index 0000000..afa8f32 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/RecyclingBitmapDrawable.java @@ -0,0 +1,86 @@ +package com.example.nanchen.aiyaschoolpush.im; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; + +public class RecyclingBitmapDrawable extends BitmapDrawable { + static final String TAG = "CountingBitmapDrawable"; + + private int mCacheRefCount = 0; + private int mDisplayRefCount = 0; + + private boolean mHasBeenDisplayed; + + public RecyclingBitmapDrawable(Resources res, Bitmap bitmap) { + super(res, bitmap); + } + + /** + * Notify the drawable that the displayed state has changed. Internally a + * count is kept so that the drawable knows when it is no longer being + * displayed. + * + * @param isDisplayed + * - Whether the drawable is being displayed or not + */ + public void setIsDisplayed(boolean isDisplayed) { + // BEGIN_INCLUDE(set_is_displayed) + synchronized (this) { + if (isDisplayed) { + mDisplayRefCount++; + mHasBeenDisplayed = true; + } else { + mDisplayRefCount--; + } + } + + // Check to see if recycle() can be called + checkState(); + // END_INCLUDE(set_is_displayed) + } + + /** + * Notify the drawable that the cache state has changed. Internally a count + * is kept so that the drawable knows when it is no longer being cached. + * + * @param isCached + * - Whether the drawable is being cached or not + */ + public void setIsCached(boolean isCached) { + // BEGIN_INCLUDE(set_is_cached) + synchronized (this) { + if (isCached) { + mCacheRefCount++; + } else { + mCacheRefCount--; + } + } + + // Check to see if recycle() can be called + checkState(); + // END_INCLUDE(set_is_cached) + } + + private synchronized void checkState() { + // BEGIN_INCLUDE(check_state) + // If the drawable cache and display ref counts = 0, and this drawable + // has been displayed, then recycle + if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed + && hasValidBitmap()) { +// if (BuildConfig.DEBUG) { +// Log.d(TAG, "No longer being used or cached so recycling. " +// + toString()); +// } + + getBitmap().recycle(); + } + // END_INCLUDE(check_state) + } + + private synchronized boolean hasValidBitmap() { + Bitmap bitmap = getBitmap(); + return bitmap != null && !bitmap.isRecycled(); + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/UserProfileManager.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/UserProfileManager.java new file mode 100644 index 0000000..ab59fb0 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/UserProfileManager.java @@ -0,0 +1,180 @@ +package com.example.nanchen.aiyaschoolpush.im; + +import android.content.Context; + +import com.example.nanchen.aiyaschoolpush.helper.DemoHelper; +import com.hyphenate.EMValueCallBack; +import com.hyphenate.chat.EMClient; +import com.hyphenate.easeui.domain.EaseUser; + +import java.util.ArrayList; +import java.util.List; + +public class UserProfileManager { + + /** + * application context + */ + protected Context appContext = null; + + /** + * init flag: test if the sdk has been inited before, we don't need to init + * again + */ + private boolean sdkInited = false; + + /** + * HuanXin sync contact nick and avatar listener + */ + private List syncContactInfosListeners; + + private boolean isSyncingContactInfosWithServer = false; + + private EaseUser currentUser; + + public UserProfileManager() { + } + + public synchronized boolean init(Context context) { + if (sdkInited) { + return true; + } + ParseManager.getInstance().onInit(context); + syncContactInfosListeners = new ArrayList(); + sdkInited = true; + return true; + } + + public void addSyncContactInfoListener(DataSyncListener listener) { + if (listener == null) { + return; + } + if (!syncContactInfosListeners.contains(listener)) { + syncContactInfosListeners.add(listener); + } + } + + public void removeSyncContactInfoListener(DataSyncListener listener) { + if (listener == null) { + return; + } + if (syncContactInfosListeners.contains(listener)) { + syncContactInfosListeners.remove(listener); + } + } + + public void asyncFetchContactInfosFromServer(List usernames, final EMValueCallBack> callback) { + if (isSyncingContactInfosWithServer) { + return; + } + isSyncingContactInfosWithServer = true; + ParseManager.getInstance().getContactInfos(usernames, new EMValueCallBack>() { + + @Override + public void onSuccess(List value) { + isSyncingContactInfosWithServer = false; + // in case that logout already before server returns,we should + // return immediately + if (!DemoHelper.getInstance().isLoggedIn()) { + return; + } + if (callback != null) { + callback.onSuccess(value); + } + } + + @Override + public void onError(int error, String errorMsg) { + isSyncingContactInfosWithServer = false; + if (callback != null) { + callback.onError(error, errorMsg); + } + } + + }); + + } + + public void notifyContactInfosSyncListener(boolean success) { + for (DataSyncListener listener : syncContactInfosListeners) { + listener.onSyncComplete(success); + } + } + + public boolean isSyncingContactInfoWithServer() { + return isSyncingContactInfosWithServer; + } + + public synchronized void reset() { + isSyncingContactInfosWithServer = false; + currentUser = null; + PreferenceManager.getInstance().removeCurrentUserInfo(); + } + + public synchronized EaseUser getCurrentUserInfo() { + if (currentUser == null) { + String username = EMClient.getInstance().getCurrentUser(); + currentUser = new EaseUser(username); + String nick = getCurrentUserNick(); + currentUser.setNick((nick != null) ? nick : username); + currentUser.setAvatar(getCurrentUserAvatar()); + } + return currentUser; + } + + public boolean updateCurrentUserNickName(final String nickname) { + boolean isSuccess = ParseManager.getInstance().updateParseNickName(nickname); + if (isSuccess) { + setCurrentUserNick(nickname); + } + return isSuccess; + } + + public String uploadUserAvatar(byte[] data) { + String avatarUrl = ParseManager.getInstance().uploadParseAvatar(data); + if (avatarUrl != null) { + setCurrentUserAvatar(avatarUrl); + } + return avatarUrl; + } + + public void asyncGetCurrentUserInfo() { + ParseManager.getInstance().asyncGetCurrentUserInfo(new EMValueCallBack() { + + @Override + public void onSuccess(EaseUser value) { + if(value != null){ + setCurrentUserNick(value.getNick()); + setCurrentUserAvatar(value.getAvatar()); + } + } + + @Override + public void onError(int error, String errorMsg) { + + } + }); + + } + public void asyncGetUserInfo(final String username,final EMValueCallBack callback){ + ParseManager.getInstance().asyncGetUserInfo(username, callback); + } + private void setCurrentUserNick(String nickname) { + getCurrentUserInfo().setNick(nickname); + PreferenceManager.getInstance().setCurrentUserNick(nickname); + } + + private void setCurrentUserAvatar(String avatar) { + getCurrentUserInfo().setAvatar(avatar); + PreferenceManager.getInstance().setCurrentUserAvatar(avatar); + } + + private String getCurrentUserNick() { + return PreferenceManager.getInstance().getCurrentUserNick(); + } + + private String getCurrentUserAvatar() { + return PreferenceManager.getInstance().getCurrentUserAvatar(); + } + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/Utils.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/Utils.java new file mode 100644 index 0000000..678b0d6 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/Utils.java @@ -0,0 +1,95 @@ +package com.example.nanchen.aiyaschoolpush.im; + +import android.annotation.SuppressLint; +import android.hardware.Camera; +import android.hardware.Camera.Parameters; +import android.hardware.Camera.Size; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.os.StrictMode; + +import com.example.nanchen.aiyaschoolpush.ui.activity.ImageGridActivity; + +import java.util.Comparator; +import java.util.List; + +public class Utils { + + private Utils() { + } + + @SuppressLint("NewApi") + public static void enableStrictMode() { + if(Utils.hasGingerbread()) + { + StrictMode.ThreadPolicy.Builder threadPolicyBuilder = + new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyLog(); + StrictMode.VmPolicy.Builder vmPolicyBuilder = + new StrictMode.VmPolicy.Builder() + .detectAll() + .penaltyLog(); + + if (Utils.hasHoneycomb()) { + threadPolicyBuilder.penaltyFlashScreen(); + vmPolicyBuilder + .setClassInstanceLimit(ImageGridActivity.class, 1); + } + StrictMode.setThreadPolicy(threadPolicyBuilder.build()); + StrictMode.setVmPolicy(vmPolicyBuilder.build()); + } + + + + + + } + + public static boolean hasFroyo() { + return Build.VERSION.SDK_INT >= VERSION_CODES.FROYO; + + } + + public static boolean hasGingerbread() { + return Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD; + } + + public static boolean hasHoneycomb() { + return Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + } + + public static boolean hasHoneycombMR1() { + return Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1; + } + + public static boolean hasJellyBean() { + return Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; + } + + public static boolean hasKitKat() { + return Build.VERSION.SDK_INT >= 19; + } + + public static List getResolutionList(Camera camera) + { + Parameters parameters = camera.getParameters(); + return parameters.getSupportedPreviewSizes(); + } + + public static class ResolutionComparator implements Comparator{ + + @Override + public int compare(Size lhs, Size rhs) { + if(lhs.height!=rhs.height) + return lhs.height-rhs.height; + else + return lhs.width-rhs.width; + } + + } + + + + +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/VideoEntity.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/VideoEntity.java new file mode 100644 index 0000000..dbbee01 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/VideoEntity.java @@ -0,0 +1,9 @@ +package com.example.nanchen.aiyaschoolpush.im; + +public class VideoEntity { + public int ID; + public String title; + public String filePath; + public int size; + public int duration; +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/model/ActivityModel.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/model/ActivityModel.java new file mode 100644 index 0000000..7376c30 --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/model/ActivityModel.java @@ -0,0 +1,36 @@ +package com.example.nanchen.aiyaschoolpush.model; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.model + * @date 2016/10/10 14:43 + */ + +public class ActivityModel { + private String title; + private int id; + + public ActivityModel(int id,String title) { + this.id = id; + this.title = title; + } + + public int getId() { + return id; + + + } + + public void setId(int id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/model/Attach.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/model/Attach.java new file mode 100644 index 0000000..43f6a9b --- /dev/null +++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/model/Attach.java @@ -0,0 +1,51 @@ +package com.example.nanchen.aiyaschoolpush.model; + +import java.io.Serializable; +import java.util.List; + +/** + * @author nanchen + * @fileName AiYaSchoolPush + * @packageName com.example.nanchen.aiyaschoolpush.model + * @date 2016/10/26 10:48 + * + * 社区话题附件 + */ + +public class Attach implements IJsonModel,Serializable { + public static final int ATTACH_TYPE_IMAGE = 0; + public static final int ATTACH_TYPE_VIDEO = 1; + + public List mImages; + public List