Skip to content
This repository was archived by the owner on Jul 18, 2026. It is now read-only.

Commit cd73e98

Browse files
committed
增加公告点击展开功能
1 parent 3532815 commit cd73e98

7 files changed

Lines changed: 251 additions & 6 deletions

File tree

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package com.yenaly.han1meviewer.logic.model
22

3+
import android.graphics.Color
4+
import android.text.SpannableString
5+
import android.text.Spanned
6+
import android.text.style.ForegroundColorSpan
37
import androidx.annotation.Keep
48
import com.yenaly.han1meviewer.LOCAL_DATE_TIME_FORMAT
59
import kotlinx.datetime.TimeZone
@@ -9,18 +13,36 @@ import kotlin.time.ExperimentalTime
913

1014
@Keep
1115
data class Announcement(
12-
@JvmField val title: String = "",
13-
@JvmField val content: String = "",
16+
@JvmField val title: String,
17+
@JvmField val content: String,
18+
@JvmField val positiveText: String ? = null,
19+
@JvmField val negativeText: String ? = null,
1420
@JvmField val timestamp: Long = 0,
15-
@JvmField val priority: Int = 2,
16-
@JvmField val imageUrl: String = "",
21+
@JvmField val priority: Int = 1,
22+
@JvmField val imageUrl: String ? = null,
1723
@JvmField val isActive: Boolean = false
1824
) {
25+
//firebase初始化需要一个空的构造函数
26+
constructor() : this("", "", null, null, 0L, 1, null, false)
1927
@OptIn(ExperimentalTime::class)
2028
fun getFormattedDate(): String {
2129
return kotlin.time.Instant
2230
.fromEpochSeconds(timestamp)
2331
.toLocalDateTime(TimeZone.currentSystemDefault())
2432
.format(LOCAL_DATE_TIME_FORMAT)
2533
}
34+
fun getHighlightedContent(): Spanned {
35+
val regex = "(https?://[\\w-]+(\\.[\\w-]+)+([/?%&=]*)?)".toRegex()
36+
val spannableContent = SpannableString(content)
37+
val matches = regex.findAll(content)
38+
39+
for (match in matches) {
40+
spannableContent.setSpan(
41+
ForegroundColorSpan(Color.BLUE),
42+
match.range.first, match.range.last + 1,
43+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
44+
)
45+
}
46+
return spannableContent
47+
}
2648
}

app/src/main/java/com/yenaly/han1meviewer/ui/fragment/home/HomePageFragment.kt

Lines changed: 158 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,32 @@
11
package com.yenaly.han1meviewer.ui.fragment.home
22

33
import android.annotation.SuppressLint
4+
import android.app.Dialog
5+
import android.content.ContentValues
6+
import android.content.Context
47
import android.content.res.ColorStateList
8+
import android.graphics.Bitmap
59
import android.graphics.Color
610
import android.graphics.RenderEffect
711
import android.graphics.Shader
812
import android.graphics.drawable.ColorDrawable
913
import android.graphics.drawable.GradientDrawable
1014
import android.os.Build
1115
import android.os.Bundle
16+
import android.os.Environment
17+
import android.provider.MediaStore
18+
import android.text.Spanned
19+
import android.text.method.LinkMovementMethod
1220
import android.util.Log
1321
import android.view.LayoutInflater
22+
import android.view.View
1423
import android.view.ViewGroup
24+
import android.widget.ImageView
25+
import android.widget.TextView
1526
import androidx.activity.OnBackPressedCallback
1627
import androidx.annotation.RequiresApi
1728
import androidx.appcompat.app.AlertDialog
29+
import androidx.core.graphics.drawable.toBitmap
1830
import androidx.core.graphics.drawable.toBitmapOrNull
1931
import androidx.core.os.bundleOf
2032
import androidx.core.view.ViewCompat
@@ -34,8 +46,12 @@ import androidx.palette.graphics.Palette
3446
import androidx.recyclerview.widget.ConcatAdapter
3547
import androidx.recyclerview.widget.LinearLayoutManager
3648
import androidx.recyclerview.widget.RecyclerView
49+
import coil.ImageLoader
3750
import coil.load
51+
import coil.request.ImageRequest
52+
import coil.request.SuccessResult
3853
import com.google.android.material.dialog.MaterialAlertDialogBuilder
54+
import com.google.android.material.imageview.ShapeableImageView
3955
import com.yenaly.han1meviewer.ADVANCED_SEARCH_MAP
4056
import com.yenaly.han1meviewer.HAdvancedSearch
4157
import com.yenaly.han1meviewer.R
@@ -61,7 +77,12 @@ import com.yenaly.yenaly_libs.utils.application
6177
import com.yenaly.yenaly_libs.utils.getSpValue
6278
import com.yenaly.yenaly_libs.utils.putSpValue
6379
import com.yenaly.yenaly_libs.utils.showShortToast
80+
import kotlinx.coroutines.CoroutineScope
81+
import kotlinx.coroutines.Dispatchers
6482
import kotlinx.coroutines.launch
83+
import kotlinx.coroutines.withContext
84+
import java.io.File
85+
import java.io.FileOutputStream
6586
import java.io.Serializable
6687

6788
/**
@@ -680,8 +701,17 @@ class HomePageFragment : YenalyFragment<FragmentHomePageBinding>(),
680701
}
681702
announcementCardAdapter = AnnouncementCardAdapter(
682703
sortedList,
683-
onClick = {
684-
showShortToast("点击事件正在绝赞制作中!")
704+
onClick = { item ->
705+
showAnnouncementDialog(
706+
requireContext(),
707+
title = item.title,
708+
content = item.getHighlightedContent(),
709+
imageUrl = item.imageUrl,
710+
positiveText = item.positiveText,
711+
positiveAction = { },
712+
negativeText = item.negativeText,
713+
negativeAction = { },
714+
)
685715
},
686716
onClose = {
687717
putSpValue("last_dismiss_time", System.currentTimeMillis(),"setting_pref")
@@ -701,4 +731,130 @@ class HomePageFragment : YenalyFragment<FragmentHomePageBinding>(),
701731
}
702732
// viewModel.loadAnnouncements()
703733
}
734+
fun showAnnouncementDialog(
735+
context: Context,
736+
title: String,
737+
content: Spanned,
738+
imageUrl: String? = null,
739+
positiveText: String? = null,
740+
positiveAction: (() -> Unit)? = null,
741+
negativeText: String? = null,
742+
negativeAction: (() -> Unit)? = null
743+
) {
744+
val dialogView = LayoutInflater.from(context)
745+
.inflate(R.layout.dialog_announcement, null, false)
746+
747+
val tvTitle = dialogView.findViewById<TextView>(R.id.dialogTitle)
748+
val tvContent = dialogView.findViewById<TextView>(R.id.dialogContent)
749+
val ivImage = dialogView.findViewById<ShapeableImageView>(R.id.dialogImage)
750+
val positiveText = positiveText ?: context.getString(R.string.i_understand)
751+
752+
tvTitle.text = title
753+
tvTitle.visibility = View.VISIBLE
754+
755+
tvContent.text = content
756+
tvContent.movementMethod = LinkMovementMethod.getInstance()
757+
tvContent.highlightColor = Color.TRANSPARENT
758+
759+
if (!imageUrl.isNullOrBlank()) {
760+
ivImage.visibility = View.VISIBLE
761+
ivImage.load(imageUrl) {
762+
placeholder(R.drawable.akarin)
763+
error(R.drawable.baseline_error_outline_24)
764+
}
765+
ivImage.setOnClickListener {
766+
val fullScreenDialog = Dialog(requireContext(), android.R.style.Theme_Black_NoTitleBar_Fullscreen)
767+
val fullImageView = ImageView(requireContext()).apply {
768+
layoutParams = ViewGroup.LayoutParams(
769+
ViewGroup.LayoutParams.MATCH_PARENT,
770+
ViewGroup.LayoutParams.MATCH_PARENT
771+
)
772+
scaleType = ImageView.ScaleType.FIT_CENTER
773+
load(imageUrl)
774+
}
775+
776+
fullScreenDialog.setContentView(fullImageView)
777+
778+
fullImageView.scaleX = 0.8f
779+
fullImageView.scaleY = 0.8f
780+
fullImageView.animate()
781+
.scaleX(1f)
782+
.scaleY(1f)
783+
.setDuration(200)
784+
.start()
785+
786+
fullImageView.setOnClickListener {
787+
fullImageView.animate()
788+
.scaleX(0.8f)
789+
.scaleY(0.8f)
790+
.setDuration(200)
791+
.withEndAction { fullScreenDialog.dismiss() }
792+
.start()
793+
}
794+
795+
fullImageView.setOnLongClickListener {
796+
MaterialAlertDialogBuilder(context)
797+
.setTitle(getString(R.string.save_image_confirm))
798+
.setPositiveButton(getString(R.string.sure)) { _, _ ->
799+
saveImageToGallery(imageUrl)
800+
}
801+
.setNegativeButton(getString(R.string.cancel)){ dialog, _ -> dialog.dismiss() }
802+
.create()
803+
.show()
804+
true
805+
}
806+
807+
fullScreenDialog.show()
808+
}
809+
}
810+
811+
val builder = MaterialAlertDialogBuilder(context)
812+
.setView(dialogView)
813+
builder.setPositiveButton(positiveText) { _, _ ->
814+
positiveAction?.invoke()
815+
}
816+
817+
if (!negativeText.isNullOrBlank()) {
818+
builder.setNegativeButton(negativeText) { _, _ ->
819+
negativeAction?.invoke()
820+
}
821+
}
822+
823+
builder.show()
824+
}
825+
private fun saveImageToGallery(imageUrl: String) {
826+
CoroutineScope(Dispatchers.IO).launch {
827+
val loader = ImageLoader(requireContext())
828+
val request = ImageRequest.Builder(requireContext())
829+
.data(imageUrl)
830+
.allowHardware(false)
831+
.build()
832+
val result = (loader.execute(request) as? SuccessResult)?.drawable?.toBitmap()
833+
result?.let { bitmap ->
834+
val filename = "IMG_${System.currentTimeMillis()}.jpg"
835+
val fos = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
836+
val contentValues = ContentValues().apply {
837+
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
838+
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
839+
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
840+
}
841+
val uri = requireContext().contentResolver.insert(
842+
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
843+
contentValues
844+
)
845+
uri?.let { requireContext().contentResolver.openOutputStream(it) }
846+
} else {
847+
val file = File(
848+
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
849+
filename
850+
)
851+
FileOutputStream(file)
852+
}
853+
fos?.use { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it) }
854+
withContext(Dispatchers.Main) {
855+
showShortToast(getString(R.string.saved))
856+
}
857+
}
858+
}
859+
}
704860
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="1024" android:viewportWidth="1024" android:width="24dp">
2+
3+
<path android:fillColor="?attr/colorPrimary" android:pathData="M558.1,251.7c40,0 77.7,15.6 106,43.9 28.3,28.3 44,65.9 44,106l0.2,171.4c0,11.1 2.6,22.2 7.6,32.2L793.4,760c0.5,1 2,4 -0.3,7.8s-5.7,3.8 -6.8,3.8H671l-99.3,0.1 -119.5,0.1H388l-149.6,0.1h-0.1c-1.1,0 -4.5,0 -6.8,-3.8s-0.8,-6.8 -0.3,-7.8l77.4,-154.6c5,-10 7.6,-21.2 7.6,-32.3L316,402c0,-40.1 15.5,-77.8 43.8,-106.1 28.3,-28.4 66,-44 106,-44l92.1,-0.1h0.2m0,-60.1h-0.2l-92.1,0.1C349.8,191.9 255.9,286 256,402l0.2,171.4c0,1.9 -0.4,3.7 -1.3,5.4l-77.4,154.6c-22.6,45.2 10.2,98.4 60.8,98.4h0.1l149.7,-0.1c14,55.4 64.2,96.4 123.9,96.4 59.8,0 110,-41.1 124,-96.6h-21.7H636l150.4,-0.1c50.5,0 83.4,-53.3 60.8,-98.5l-77.5,-154.8c-0.8,-1.7 -1.3,-3.5 -1.3,-5.4l-0.2,-171.4C768,285.6 674,191.7 558.1,191.7zM452.2,831.8l119.5,-0.1c-11.3,21.5 -33.8,36.2 -59.8,36.2 -25.9,0 -48.4,-14.7 -59.7,-36.1H426h26.2z"/>
4+
5+
<path android:fillColor="?attr/colorPrimary" android:pathData="M512.9,98.4c-55.5,0 -100.5,44.9 -100.7,100.4 17.1,-4.5 35,-6.9 53.5,-7h6.1c4.3,-18.1 20.6,-31.6 40.1,-31.6 19.4,0 35.7,13.5 40.1,31.6h6c19.2,0 37.8,2.6 55.5,7.4v-0.1c0,-55.6 -45,-100.7 -100.6,-100.7z"/>
6+
7+
</vector>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:layout_width="match_parent"
4+
android:layout_height="wrap_content"
5+
xmlns:app="http://schemas.android.com/apk/res-auto">
6+
7+
<LinearLayout
8+
android:orientation="vertical"
9+
android:padding="16dp"
10+
android:layout_width="match_parent"
11+
android:layout_height="wrap_content"
12+
android:gravity="center_horizontal">
13+
14+
<ImageView
15+
android:layout_width="40dp"
16+
android:layout_height="40dp"
17+
android:importantForAccessibility="no"
18+
android:src="@drawable/ic_baseline_alert_24"
19+
app:tint="?attr/colorPrimary" />
20+
21+
<TextView
22+
android:id="@+id/dialogTitle"
23+
android:layout_width="match_parent"
24+
android:layout_height="wrap_content"
25+
android:textAppearance="?attr/textAppearanceHeadline6"
26+
android:textColor="?attr/colorOnSurface"
27+
android:paddingBottom="8dp"
28+
android:visibility="gone"/>
29+
30+
<TextView
31+
android:id="@+id/dialogContent"
32+
android:layout_width="match_parent"
33+
android:layout_height="wrap_content"
34+
android:textColor="?attr/colorOnSurface"
35+
android:paddingBottom="8dp"
36+
android:autoLink="web"
37+
android:linksClickable="true"/>
38+
39+
<com.google.android.material.imageview.ShapeableImageView
40+
android:id="@+id/dialogImage"
41+
android:layout_width="match_parent"
42+
android:layout_height="200dp"
43+
android:importantForAccessibility="no"
44+
android:scaleType="centerCrop"
45+
android:visibility="gone"
46+
app:shapeAppearanceOverlay="@style/RoundCornerImageView"/>
47+
</LinearLayout>
48+
</ScrollView>

app/src/main/res/values-en/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,4 +426,8 @@ Recommendations are as follows:\n
426426
<string name="permission_error">Permission error, please try to re-specify the folder!</string>
427427
<string name="resume_playback_title">Resume Playback</string>
428428
<string name="resume_playback_summary">Automatically jump to the last playback position if there is play history</string>
429+
<string name="saved">Saved</string>
430+
<string name="i_understand">I understand</string>
431+
<string name="close">Close</string>
432+
<string name="save_image_confirm">Save image?</string>
429433
</resources>

app/src/main/res/values-zh-rCN/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,4 +432,8 @@
432432
<string name="permission_error">权限错误,请尝试重新指定文件夹!</string>
433433
<string name="resume_playback_title">续播功能</string>
434434
<string name="resume_playback_summary">如有播放记录,将自动跳转到上次退出时的位置</string>
435+
<string name="saved">已保存</string>
436+
<string name="i_understand">我明白了</string>
437+
<string name="close">关闭</string>
438+
<string name="save_image_confirm">是否保存图片?</string>
435439
</resources>

app/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,4 +455,8 @@
455455
<string name="permission_error">權限錯誤,請嘗試重新指定資料夾!</string>
456456
<string name="resume_playback_title">續播功能</string>
457457
<string name="resume_playback_summary">如有播放記錄,將自動跳轉到上次退出位置</string>
458+
<string name="saved">已儲存</string>
459+
<string name="i_understand">我明白了</string>
460+
<string name="close">關閉</string>
461+
<string name="save_image_confirm">是否儲存圖片?</string>
458462
</resources>

0 commit comments

Comments
 (0)