diff --git a/app/build.gradle b/app/build.gradle index 47a30ef..e62bcf1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,15 +47,37 @@ android { signingConfig signingConfigs.release // 移除无用的resource文件 // shrinkResources false + buildConfigField "String", "BASE_URL", '"https://oa.qbjjyyun.net/api"' + buildConfigField "String", "M_URL", '"https://m.qbjjyyun.net"' + buildConfigField "String", "APP_NAME", '"家校互通"' + +// buildConfigField "String", "BASE_URL", '"http://192.168.69.99:9009"' +// buildConfigField "String", "M_URL", '"http://192.168.69.99:8098"' +// buildConfigField "String", "APP_NAME", '"家校互通(测试)"' } debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' +// debuggable true + signingConfig signingConfigs.release + // 移除无用的resource文件 +// shrinkResources false + buildConfigField "String", "BASE_URL", '"http://192.168.69.99:9009"' + buildConfigField "String", "M_URL", '"http://192.168.69.112:8098"' + buildConfigField "String", "APP_NAME", '"家校互通(开发)"' + } + + yzx_test { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' debuggable true - signingConfig signingConfigs.yzx + signingConfig signingConfigs.release // 移除无用的resource文件 - shrinkResources false +// shrinkResources false + buildConfigField "String", "BASE_URL", '"http://192.168.69.99:9009"' + buildConfigField "String", "M_URL", '"http://192.168.69.99:8098"' + buildConfigField "String", "APP_NAME", '"家校互通(测试)"' } applicationVariants.all { variant -> @@ -65,6 +87,7 @@ android { output.outputFileName = fileName } } + } lintOptions { @@ -102,4 +125,6 @@ dependencies { // 友盟统计SDK implementation 'com.umeng.umsdk:common:9.3.6' // 必选 implementation 'com.umeng.umsdk:asms:1.2.1' // 必选 + implementation 'com.github.thomhurst:RoundImageView:1.0.2' + implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1876c5b..2a17176 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,7 +13,6 @@ - - @@ -39,6 +34,14 @@ + + - - + android:screenOrientation="portrait" /> >() { override fun initView() { btn1.setOnClickListener { - WebActivity.active(this, "https://m.qbjjyyun.net/") + Config.APP_NAME = "家校互通" + Config.BASE_URL = "https://oa.qbjjyyun.net/api" + Config.M_URL = "https://m.qbjjyyun.net" + User.clearUserInfo() + startActivity() } btn2.setOnClickListener { - WebActivity.active(this, "https://m.live.educlouddata.com/") + Config.APP_NAME = "家校互通(LIVE)" + Config.BASE_URL = "https://oa.live.educlouddata.com/api" + Config.M_URL = "https://m.live.educlouddata.com" + User.clearUserInfo() + startActivity() } btn6.setOnClickListener { - WebActivity.active(this, "http://192.168.69.200:8098/") + Config.APP_NAME = "家校互通(测试)" + Config.BASE_URL = "http://192.168.69.99:9009" + Config.M_URL = "http://192.168.69.99:8098" + User.clearUserInfo() + startActivity() } btn3.setOnClickListener { - WebActivity.active(this, editText.text.toString()) + val mBaseUrl = mBaseUrlTv.text.toString() + if(StringUtils.isTrimEmpty(mBaseUrl)){ + toast("请输入BASE_URL") + return@setOnClickListener + } + val mUrl = mUrlTv.text.toString() + if(StringUtils.isTrimEmpty(mUrl)){ + toast("请输入M_URL") + return@setOnClickListener + } + Config.APP_NAME = "家校互通(本地)" + Config.BASE_URL = mBaseUrl + Config.M_URL = mUrl + User.clearUserInfo() + startActivity() } btn4.setOnClickListener { BookActivity.active( diff --git a/app/src/main/java/com/yzx/webebook/activity/HomeActivity.kt b/app/src/main/java/com/yzx/webebook/activity/HomeActivity.kt new file mode 100644 index 0000000..ee20ad4 --- /dev/null +++ b/app/src/main/java/com/yzx/webebook/activity/HomeActivity.kt @@ -0,0 +1,251 @@ +package com.yzx.webebook.activity + +import android.annotation.SuppressLint +import android.app.AlertDialog +import android.app.Dialog +import android.content.Context +import android.content.DialogInterface +import android.util.Log +import android.view.View +import android.widget.TextView +import androidx.core.widget.ContentLoadingProgressBar +import androidx.recyclerview.widget.GridLayoutManager +import com.allenliu.versionchecklib.v2.AllenVersionChecker +import com.allenliu.versionchecklib.v2.builder.UIData +import com.allenliu.versionchecklib.v2.callback.CustomDownloadingDialogListener +import com.blankj.utilcode.util.AppUtils +import com.blankj.utilcode.util.NetworkUtils +import com.blankj.utilcode.util.SPUtils +import com.blankj.utilcode.util.StringUtils +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.lzy.okgo.OkGo +import com.lzy.okgo.callback.StringCallback +import com.lzy.okgo.model.Response +import com.yzx.webebook.MainActivity +import com.yzx.webebook.R +import com.yzx.webebook.activity.base.BaseActivity +import com.yzx.webebook.adapter.HomeAdapter +import com.yzx.webebook.config.Config +import com.yzx.webebook.model.BaseBean +import com.yzx.webebook.model.HomeItem +import com.yzx.webebook.model.User +import com.yzx.webebook.model.Version +import com.yzx.webebook.presenter.base.BasePresenter +import com.yzx.webebook.widget.BaseDialog +import kotlinx.android.synthetic.main.activity_home.* +import kotlinx.android.synthetic.main.activity_home.titleTv +import org.jetbrains.anko.find +import org.jetbrains.anko.startActivity +import org.jetbrains.anko.toast +import java.util.* + +class HomeActivity : BaseActivity>() { + + private val mAdapter: HomeAdapter by lazy { + val list = mutableListOf() + list.add(HomeItem("家庭作业", R.mipmap.ic_home_work, "/homework/ebook_list")) + list.add(HomeItem("笔记本", R.mipmap.ic_home_notebook, "/notebook/list")) + list.add(HomeItem("背诵默写", R.mipmap.ic_home_write, "/write/index")) + list.add(HomeItem("书架", R.mipmap.ic_home_rack)) + list.add(HomeItem("错题本", R.mipmap.ic_home_errorbook, "/errorbook/ebookindex")) + list.add(HomeItem("我的", R.mipmap.ic_home_my, "/mine/ebook")) + HomeAdapter(list) + } + + private var count = 0 + private val run = Runnable { + count = 0 + } + + override val inflateId: Int + get() = R.layout.activity_home + + override fun initView() { + val layoutManager = GridLayoutManager(this, 3) + homeGridView.layoutManager = layoutManager + homeGridView.adapter = mAdapter + + switchBtn.setOnClickListener { + val url = "${Config.M_URL}/choiceunit.html?from=ehome" + WebActivity.active(this, url) + } + + loginBtn.setOnClickListener { + val url = "${Config.M_URL}/login.html?from=ehome" + WebActivity.active(this, url) + } + + testBtn.setOnClickListener { + count++ + testBtn.removeCallbacks(run) + testBtn.postDelayed(run, 1000) + if (count >= 5) { + startActivity() + } + } + } + + override fun initData() { + mAdapter.setOnItemClickListener { adapter, view, position -> + if (User.getUser().user_id == 0) { + val url = "${Config.M_URL}/login.html?from=ehome" + WebActivity.active(this, url) + } else { + val list = mAdapter.data + val item = list[position] + if (StringUtils.isTrimEmpty(item.url)) { + toast("功能设计中,敬请期待") + } else { + val url = Config.M_URL + item.url + WebActivity.active(this, url) + } + } + } + } + + override fun initPresenter(): BasePresenter<*>? { + return null + } + + @SuppressLint("SetTextI18n") + override fun onResume() { + super.onResume() + val user = User.getUser() + if (user.user_id != 0) { + userNameTv.text = "你好,${user.user_name}" + userInfoTv.text = "${user.school_name} | ${user.grade_name}${user.class_name}" + userInfoTv.visibility = View.VISIBLE + loginBtn.visibility = View.GONE + switchBtn.visibility = if (User.getUnitCount() > 1) { + View.VISIBLE + } else { + View.GONE + } + } else { + userNameTv.text = "未登录" + userInfoTv.text = "" + userInfoTv.visibility = View.GONE + loginBtn.visibility = View.VISIBLE + switchBtn.visibility = View.GONE + } + titleTv.text = Config.APP_NAME + if (NetworkUtils.isConnected()) { + checkVersion(false) + }else{ + checkNetwork() + } + } + + private fun checkNetwork() { + val builder = AlertDialog.Builder(this) + builder.setTitle("温馨提示") + builder.setMessage("你还没有链接网络,请先连接网络!") + builder.setPositiveButton("去设置") { _, _ -> + NetworkUtils.openWirelessSettings() + } + builder.setCancelable(false) + val dialog = builder.create() + dialog.setCanceledOnTouchOutside(false) + dialog.getButton(DialogInterface.BUTTON_NEGATIVE)?.visibility = View.GONE + dialog.show() + } + + private fun checkVersion(userCheck: Boolean = false): Unit { + val BASE_URL = Config.BASE_URL + OkGo.post("$BASE_URL/parent/common/getInkBottleMaxVersion") + .tag(this) + .execute(object : StringCallback() { + + override fun onSuccess(response: Response) { + val json = response.body() + Log.d("checkVersion", "onSuccess: ${json}") + val resultType = object : TypeToken>() {}.type + val gson = Gson() + val res = gson.fromJson>(json, resultType) + if (res.code == 0) { + val appVersion = AppUtils.getAppVersionName() + val appVersionCode = AppUtils.getAppVersionCode() + val versionCode = res?.data?.version_code ?: 0 + val curr = Date().time + val lastTipTime = SPUtils.getInstance() + .getLong("last_tip_time${res.data.version_code}", 0) + if (versionCode > appVersionCode) { //有新版本 + when { + res.data.type == 1 -> { //强制升级 + updateApp(res.data) + } + curr - lastTipTime > 24 * 60 * 60 * 1000 -> { //非强制一天以内提示一次 + updateApp(res.data) + } + userCheck -> { // + updateApp(res.data) + } + } + + } else { + if (userCheck) { + toast("已经是最新版本了!") + } + } + } + } + + }) + + + } + + private fun updateApp(version: Version) { + AllenVersionChecker + .getInstance() + .downloadOnly( + UIData.create() + .setTitle("检测到新版本") + .setContent(version.explain) + .setDownloadUrl(version.app_src) + ) + .setCustomVersionDialogListener { context, versionBundle -> + val dialog = BaseDialog(context, R.style.BaseDialog, R.layout.version_dialog) + val title = dialog.find(R.id.tv_title) + title.text = "检测到新版本" + val content = dialog.find(R.id.tv_msg) + content.text = version.explain + dialog.window?.setDimAmount(0.5f) + val cancelBtn = dialog.find(R.id.cancel_btn) + if (version.type == 1) { + cancelBtn.visibility = View.GONE + } + + return@setCustomVersionDialogListener dialog + } + .setOnCancelListener { + SPUtils.getInstance().put("last_tip_time${version.version_code}", Date().time) + } + .setCustomDownloadingDialogListener(object : CustomDownloadingDialogListener { + @SuppressLint("SetTextI18n") + override fun updateUI(dialog: Dialog?, progress: Int, versionBundle: UIData?) { + val tvProgress: TextView = dialog!!.findViewById(R.id.tv_progress) + val progressBar: ContentLoadingProgressBar = dialog.findViewById(R.id.pb) + progressBar.progress = progress + tvProgress.text = "$progress%" + + Log.d("checkVersion", "onSuccess: ${progress}") + } + + override fun getCustomDownloadingDialog( + context: Context?, + progress: Int, + versionBundle: UIData? + ): Dialog { + return BaseDialog( + context!!, + R.style.BaseDialog, + R.layout.download_dialog + ) + } + + }) + .executeMission(this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/yzx/webebook/activity/WebActivity.kt b/app/src/main/java/com/yzx/webebook/activity/WebActivity.kt index 5fb550f..9cc5689 100644 --- a/app/src/main/java/com/yzx/webebook/activity/WebActivity.kt +++ b/app/src/main/java/com/yzx/webebook/activity/WebActivity.kt @@ -32,7 +32,9 @@ import com.lzy.okgo.model.Response import com.yzx.webebook.MainActivity import com.yzx.webebook.R import com.yzx.webebook.activity.base.BaseActivity +import com.yzx.webebook.config.Config import com.yzx.webebook.model.BaseBean +import com.yzx.webebook.model.User import com.yzx.webebook.model.Version import com.yzx.webebook.presenter.base.BasePresenter import com.yzx.webebook.widget.BaseDialog @@ -79,9 +81,7 @@ class WebActivity : BaseActivity>() { } fun checkVersion(userCheck: Boolean = false): Unit { - val test = "192.168.69.112:9009" - val online = "https://oa.qbjjyyun.net/api" - val BASE_URL = online + val BASE_URL = Config.BASE_URL OkGo.post("$BASE_URL/parent/common/getInkBottleMaxVersion") .tag(this) .execute(object : StringCallback() { @@ -132,8 +132,6 @@ class WebActivity : BaseActivity>() { UIData.create() .setTitle("检测到新版本") .setContent(version.explain) -// .setDownloadUrl("https://oa-edu-1259243469.cos.ap-chengdu.myqcloud.com/public/Yzx_2.0.0.apk") -// .setDownloadUrl("https://filea.oa.qbjjyyun.net/edufile/202102/5986/27a58c0b85cb42aee37ce85fe956b743.apk") .setDownloadUrl(version.app_src) ) .setCustomVersionDialogListener { context, versionBundle -> @@ -183,17 +181,18 @@ class WebActivity : BaseActivity>() { @SuppressLint("SetJavaScriptEnabled") override fun initView() { url = intent?.getStringExtra("link") ?: "https://m.qbjjyyun.net/" - titleTv.setOnClickListener { - count++ - titleTv.removeCallbacks(run) - titleTv.postDelayed(run, 1000) - if (count >= 5) { - startActivity() - } - } +// titleTv.setOnClickListener { +// count++ +// titleTv.removeCallbacks(run) +// titleTv.postDelayed(run, 1000) +// if (count >= 5) { +// startActivity() +// } +// } btnBack.setOnClickListener { onBackPressed() } btnClose.setOnClickListener { - webView.loadUrl(url) +// webView.loadUrl(url) + finish() } btnRefresh.setOnClickListener { webView.reload() @@ -261,11 +260,11 @@ class WebActivity : BaseActivity>() { view?.post { progressbar.visibility = View.GONE if (view.canGoBack()) { - btnBack.visibility = View.VISIBLE -// btnClose.visibility = View.VISIBLE +// btnBack.visibility = View.VISIBLE + btnClose.visibility = View.VISIBLE } else { - btnBack.visibility = View.GONE -// btnClose.visibility = View.GONE +// btnBack.visibility = View.GONE + btnClose.visibility = View.GONE } } } else { @@ -436,6 +435,47 @@ class YzxJavascriptInterface(var ctx: Activity) { val act = ctx as WebActivity act.checkVersion(true) } + + /** + * 保存用户信息 + */ + @JavascriptInterface + fun saveUserInfo(json:String){ + val resultType = object : TypeToken() {}.type + val gson = Gson() + val user = gson.fromJson(json, resultType) + user?.save() + } + + @JavascriptInterface + fun saveUnitCount(count:Int){ + User.saveUnitCount(count) + } + + @JavascriptInterface + fun toHome(){ + ctx.startActivity() + } + + @JavascriptInterface + fun logout(){ + val sp = SPUtils.getInstance() + sp.remove("user_name") + sp.remove("user_id") + sp.remove("type") + sp.remove("token") + sp.remove("school_id") + sp.remove("school_name") + sp.remove("phone") + sp.remove("grade_name") + sp.remove("grade_id") + sp.remove("class_name") + sp.remove("class_id") + sp.remove("ticket") + sp.remove("login_start") + sp.remove("unit_count") + ctx.startActivity() + } } diff --git a/app/src/main/java/com/yzx/webebook/adapter/HomeAdapter.kt b/app/src/main/java/com/yzx/webebook/adapter/HomeAdapter.kt new file mode 100644 index 0000000..aadd092 --- /dev/null +++ b/app/src/main/java/com/yzx/webebook/adapter/HomeAdapter.kt @@ -0,0 +1,14 @@ +package com.yzx.webebook.adapter + +import com.chad.library.adapter.base.BaseQuickAdapter +import com.chad.library.adapter.base.viewholder.BaseViewHolder +import com.yzx.webebook.R +import com.yzx.webebook.model.HomeItem +import kotlinx.android.synthetic.main.item_home.view.* + +class HomeAdapter(list: MutableList) : BaseQuickAdapter(R.layout.item_home,list) { + override fun convert(holder: BaseViewHolder, item: HomeItem) { + holder.itemView.img.setImageResource(item.img) + holder.itemView.name.text = item.title + } +} \ No newline at end of file diff --git a/app/src/main/java/com/yzx/webebook/config/Config.kt b/app/src/main/java/com/yzx/webebook/config/Config.kt new file mode 100644 index 0000000..cdb8631 --- /dev/null +++ b/app/src/main/java/com/yzx/webebook/config/Config.kt @@ -0,0 +1,9 @@ +package com.yzx.webebook.config + +import com.yzx.webebook.BuildConfig + +object Config { + var BASE_URL = BuildConfig.BASE_URL + var APP_NAME = BuildConfig.APP_NAME + var M_URL = BuildConfig.M_URL +} \ No newline at end of file diff --git a/app/src/main/java/com/yzx/webebook/model/HomeItem.kt b/app/src/main/java/com/yzx/webebook/model/HomeItem.kt new file mode 100644 index 0000000..739d2e8 --- /dev/null +++ b/app/src/main/java/com/yzx/webebook/model/HomeItem.kt @@ -0,0 +1,7 @@ +package com.yzx.webebook.model + +class HomeItem ( + var title:String, + var img:Int, + var url:String = "" +) \ No newline at end of file diff --git a/app/src/main/java/com/yzx/webebook/model/User.kt b/app/src/main/java/com/yzx/webebook/model/User.kt new file mode 100644 index 0000000..28ac934 --- /dev/null +++ b/app/src/main/java/com/yzx/webebook/model/User.kt @@ -0,0 +1,82 @@ +package com.yzx.webebook.model + +import android.content.Context +import com.blankj.utilcode.util.SPUtils + +class User( + var user_name: String, + var user_id: Int, + var type: Int, + var token: String, + var school_id: Int, + var school_name: String, + var phone: String, + var grade_name: String, + var grade_id: Int, + var class_name: String, + var class_id: Int, + var ticket: String, + var login_start: String +) { + fun save() { + val sp = SPUtils.getInstance() + sp.put("user_name", user_name) + sp.put("user_id", user_id) + sp.put("type", type) + sp.put("token", token) + sp.put("school_id", school_id) + sp.put("school_name", school_name) + sp.put("phone", phone) + sp.put("grade_name", grade_name) + sp.put("grade_id", grade_id) + sp.put("class_name", class_name) + sp.put("class_id", class_id) + sp.put("ticket", ticket) + sp.put("login_start", login_start) + } + + companion object { + fun getUser(): User { + val sp = SPUtils.getInstance() + return User( + sp.getString("user_name", ""), + sp.getInt("user_id", 0), + sp.getInt("type", 0), + sp.getString("token", ""), + sp.getInt("school_id", 0), + sp.getString("school_name", ""), + sp.getString("phone", ""), + sp.getString("grade_name", ""), + sp.getInt("grade_id", 0), + sp.getString("class_name", ""), + sp.getInt("class_id", 0), + sp.getString("ticket", ""), + sp.getString("login_start", "") + ) + } + + fun getUnitCount():Int = SPUtils.getInstance().getInt("unit_count",1) + + fun saveUnitCount(count:Int){ + SPUtils.getInstance().put("unit_count",count); + } + + fun clearUserInfo(){ + val sp = SPUtils.getInstance() + sp.remove("user_name") + sp.remove("user_id") + sp.remove("type") + sp.remove("token") + sp.remove("school_id") + sp.remove("school_name") + sp.remove("phone") + sp.remove("grade_name") + sp.remove("grade_id") + sp.remove("class_name") + sp.remove("class_id") + sp.remove("ticket") + sp.remove("login_start") + sp.remove("unit_count") + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_switch_btn.xml b/app/src/main/res/drawable/shape_switch_btn.xml new file mode 100644 index 0000000..24236b5 --- /dev/null +++ b/app/src/main/res/drawable/shape_switch_btn.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml new file mode 100644 index 0000000..b6ee42a --- /dev/null +++ b/app/src/main/res/layout/activity_home.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 24fa0c6..fe224fc 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -52,17 +52,53 @@ android:id="@+id/btn6" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="家校本地" + android:text="家校测试" android:layout_marginTop="@dimen/d_20"/> - + android:gravity="center" + android:paddingHorizontal="@dimen/d_15"> + + + + + + + + + + android:visibility="visible"/> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-xhdpi/ic_home_errorbook.png b/app/src/main/res/mipmap-xhdpi/ic_home_errorbook.png new file mode 100644 index 0000000..75b8bf8 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_home_errorbook.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_home_my.png b/app/src/main/res/mipmap-xhdpi/ic_home_my.png new file mode 100644 index 0000000..4f1ce12 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_home_my.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_home_notebook.png b/app/src/main/res/mipmap-xhdpi/ic_home_notebook.png new file mode 100644 index 0000000..e912581 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_home_notebook.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_home_rack.png b/app/src/main/res/mipmap-xhdpi/ic_home_rack.png new file mode 100644 index 0000000..b879b03 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_home_rack.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_home_work.png b/app/src/main/res/mipmap-xhdpi/ic_home_work.png new file mode 100644 index 0000000..da251c4 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_home_work.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_home_write.png b/app/src/main/res/mipmap-xhdpi/ic_home_write.png new file mode 100644 index 0000000..58221ad Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_home_write.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_unit_switch.png b/app/src/main/res/mipmap-xhdpi/ic_unit_switch.png new file mode 100644 index 0000000..d0c5dfe Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_unit_switch.png differ diff --git a/app/src/main/res/mipmap-xhdpi/student.png b/app/src/main/res/mipmap-xhdpi/student.png new file mode 100644 index 0000000..08641e2 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/student.png differ