新增:自动任务-快捷指令-警报提醒动作添加振动提醒的功能 #554 #482

This commit is contained in:
pppscn 2024-11-14 18:34:58 +08:00
parent c86f20c6de
commit 3a341f6c4e
11 changed files with 495 additions and 147 deletions

View File

@ -72,6 +72,7 @@
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:name=".App"

View File

@ -5,7 +5,9 @@ import java.io.Serializable
data class AlarmSetting(
var description: String = "", //描述
var action: String = "stop", //动作: start=启动警报, stop=停止警报
var volume: Int = 80, //播放音量
var volume: Int = 80, //播放音量0=禁用
var playTimes: Int = 1, //播放次数0=无限循环
var music: String = "", //音乐文件
var repeatTimes: Int = 5, //振动重复次数0=禁用
var vibrate: String = "---___===___", //振动律动:=强振动, -弱震动, _不振动, 时长都是100ms
) : Serializable

View File

@ -20,6 +20,7 @@ import com.idormy.sms.forwarder.databinding.FragmentTasksActionAlarmBinding
import com.idormy.sms.forwarder.entity.MsgInfo
import com.idormy.sms.forwarder.entity.TaskSetting
import com.idormy.sms.forwarder.entity.action.AlarmSetting
import com.idormy.sms.forwarder.utils.CommonUtils
import com.idormy.sms.forwarder.utils.KEY_BACK_DATA_ACTION
import com.idormy.sms.forwarder.utils.KEY_BACK_DESCRIPTION_ACTION
import com.idormy.sms.forwarder.utils.KEY_EVENT_DATA_ACTION
@ -76,7 +77,7 @@ class AlarmFragment : BaseFragment<FragmentTasksActionAlarmBinding?>(), View.OnC
mCountDownHelper = CountDownButtonHelper(binding!!.btnTest, 2)
mCountDownHelper!!.setOnCountDownListener(object : CountDownButtonHelper.OnCountDownListener {
override fun onCountDown(time: Int) {
binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time)
binding!!.btnTest.text = String.format(getString(R.string.seconds_n), time.toString())
}
override fun onFinished() {
@ -84,24 +85,55 @@ class AlarmFragment : BaseFragment<FragmentTasksActionAlarmBinding?>(), View.OnC
}
})
binding!!.sbEnableMusic.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
binding!!.layoutAlarmSettingsContent.visibility = View.VISIBLE
val volume = binding!!.xsbVolume.selectedNumber
if (volume == 0) {
binding!!.xsbVolume.setDefaultValue(80)
}
} else {
binding!!.layoutAlarmSettingsContent.visibility = View.GONE
binding!!.xsbVolume.setDefaultValue(0)
}
checkSetting(true)
}
binding!!.sbEnableVibrate.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
binding!!.layoutVibrateSettingsContent.visibility = View.VISIBLE
val repeatTimes = binding!!.xsbRepeatTimes.selectedNumber
if (repeatTimes == 0) {
binding!!.xsbRepeatTimes.setDefaultValue(5)
}
} else {
binding!!.layoutVibrateSettingsContent.visibility = View.GONE
binding!!.xsbRepeatTimes.setDefaultValue(0)
}
checkSetting(true)
}
var settingVo = AlarmSetting()
Log.d(TAG, "initViews eventData:$eventData")
if (eventData != null) {
val settingVo = Gson().fromJson(eventData, AlarmSetting::class.java)
settingVo = Gson().fromJson(eventData, AlarmSetting::class.java)
Log.d(TAG, "initViews settingVo:$settingVo")
if (settingVo.action == "start") {
binding!!.rgAlarmState.check(R.id.rb_start_alarm)
binding!!.layoutAlarmSettings.visibility = View.VISIBLE
binding!!.layoutVibrateSettings.visibility = View.VISIBLE
} else {
binding!!.rgAlarmState.check(R.id.rb_stop_alarm)
binding!!.layoutAlarmSettings.visibility = View.GONE
binding!!.layoutVibrateSettings.visibility = View.GONE
}
binding!!.xsbVolume.setDefaultValue(settingVo.volume)
binding!!.xsbLoopTimes.setDefaultValue(settingVo.playTimes)
binding!!.etMusicPath.setText(settingVo.music)
} else {
binding!!.xsbVolume.setDefaultValue(80)
binding!!.xsbLoopTimes.setDefaultValue(1)
}
binding!!.xsbVolume.setDefaultValue(settingVo.volume)
binding!!.xsbLoopTimes.setDefaultValue(settingVo.playTimes)
binding!!.etMusicPath.setText(settingVo.music)
binding!!.xsbRepeatTimes.setDefaultValue(settingVo.repeatTimes)
binding!!.etVibrationEffect.setText(settingVo.vibrate)
binding!!.sbEnableMusic.isChecked = settingVo.volume > 0
binding!!.sbEnableVibrate.isChecked = settingVo.repeatTimes > 0
}
override fun onDestroyView() {
@ -125,12 +157,29 @@ class AlarmFragment : BaseFragment<FragmentTasksActionAlarmBinding?>(), View.OnC
binding!!.layoutAlarmSettings.visibility = if (checkedId == R.id.rb_start_alarm) View.VISIBLE else View.GONE
checkSetting(true)
}
binding!!.btInsertVibrationEffect1.setOnClickListener(this)
binding!!.btInsertVibrationEffect2.setOnClickListener(this)
binding!!.btInsertVibrationEffect3.setOnClickListener(this)
}
@SingleClick
override fun onClick(v: View) {
try {
when (v.id) {
R.id.bt_insert_vibration_effect_1 -> {
CommonUtils.insertOrReplaceText2Cursor(binding!!.etVibrationEffect, "=")
return
}
R.id.bt_insert_vibration_effect_2 -> {
CommonUtils.insertOrReplaceText2Cursor(binding!!.etVibrationEffect, "-")
return
}
R.id.bt_insert_vibration_effect_3 -> {
CommonUtils.insertOrReplaceText2Cursor(binding!!.etVibrationEffect, "_")
return
}
R.id.btn_file_picker -> {
// 申请储存权限
@ -173,6 +222,10 @@ class AlarmFragment : BaseFragment<FragmentTasksActionAlarmBinding?>(), View.OnC
try {
val settingVo = checkSetting()
Log.d(TAG, settingVo.toString())
if (settingVo.volume == 0 && settingVo.repeatTimes == 0) {
XToastUtils.error(getString(R.string.alarm_settings_error))
return
}
val taskAction = TaskSetting(TASK_ACTION_ALARM, getString(R.string.task_alarm), settingVo.description, Gson().toJson(settingVo), requestCode)
val taskActionsJson = Gson().toJson(arrayListOf(taskAction))
val msgInfo = MsgInfo("task", getString(R.string.task_alarm), settingVo.description, Date(), getString(R.string.task_alarm))
@ -212,6 +265,10 @@ class AlarmFragment : BaseFragment<FragmentTasksActionAlarmBinding?>(), View.OnC
@SuppressLint("SetTextI18n")
override fun onGranted(permissions: List<String>, all: Boolean) {
val settingVo = checkSetting()
if (settingVo.volume == 0 && settingVo.repeatTimes == 0) {
XToastUtils.error(getString(R.string.alarm_settings_error))
return
}
val intent = Intent()
intent.putExtra(KEY_BACK_DESCRIPTION_ACTION, settingVo.description)
intent.putExtra(KEY_BACK_DATA_ACTION, Gson().toJson(settingVo))
@ -244,16 +301,27 @@ class AlarmFragment : BaseFragment<FragmentTasksActionAlarmBinding?>(), View.OnC
@Suppress("SameParameterValue")
@SuppressLint("SetTextI18n")
private fun checkSetting(updateView: Boolean = false): AlarmSetting {
val enableMusic = binding!!.sbEnableMusic.isChecked
val enableVibrate = binding!!.sbEnableVibrate.isChecked
val volume = binding!!.xsbVolume.selectedNumber
val loopTimes = binding!!.xsbLoopTimes.selectedNumber
val music = binding!!.etMusicPath.text.toString().trim()
val repeatTimes = binding!!.xsbRepeatTimes.selectedNumber
val vibrationEffect = binding!!.etVibrationEffect.text.toString().trim()
val description = StringBuilder()
val action = if (binding!!.rgAlarmState.checkedRadioButtonId == R.id.rb_start_alarm) {
description.append(getString(R.string.start_alarm))
description.append(", ").append(getString(R.string.alarm_volume)).append(":").append(volume).append("%")
description.append(", ").append(getString(R.string.alarm_play_times)).append(":").append(loopTimes)
if (music.isNotEmpty()) {
description.append(", ").append(getString(R.string.alarm_music)).append(":").append(music)
if (enableMusic) {
description.append(", ").append(getString(R.string.alarm_volume)).append(":").append(volume).append("%")
description.append(", ").append(getString(R.string.alarm_play_times)).append(":").append(loopTimes)
if (music.isNotEmpty()) {
description.append(", ").append(getString(R.string.alarm_music)).append(":").append(music)
}
}
if (enableVibrate) {
vibrationEffect.ifEmpty { "---___===___".also { binding!!.etVibrationEffect.setText(it) } }
description.append(", ").append(getString(R.string.alarm_vibration_effect)).append(":").append(vibrationEffect)
description.append(", ").append(getString(R.string.alarm_repeat_times)).append(":").append(repeatTimes)
}
"start"
} else {
@ -265,7 +333,7 @@ class AlarmFragment : BaseFragment<FragmentTasksActionAlarmBinding?>(), View.OnC
binding!!.tvDescription.text = description.toString()
}
return AlarmSetting(description.toString(), action, volume, loopTimes, music)
return AlarmSetting(description.toString(), action, volume, loopTimes, music, repeatTimes, vibrationEffect)
}
private fun findAudioFiles(directoryPath: String): List<String> {

View File

@ -40,6 +40,7 @@ import com.idormy.sms.forwarder.utils.INTENT_FRPC_APPLY_FILE
import com.idormy.sms.forwarder.utils.Log
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.TASK_CONDITION_CRON
import com.idormy.sms.forwarder.utils.VibrationUtils
import com.idormy.sms.forwarder.utils.task.CronJobScheduler
import com.idormy.sms.forwarder.workers.LoadAppListWorker
import com.jeremyliao.liveeventbus.LiveEventBus
@ -92,77 +93,96 @@ class ForegroundService : Service() {
})
}
// 振动控制
private lateinit var vibrationUtils: VibrationUtils
private var isVibrating = false
// 音乐播放器
private var alarmPlayer: MediaPlayer? = null
private var alarmPlayTimes = 0
private val alarmObserver = Observer<AlarmSetting> { alarm ->
Log.d(TAG, "Received alarm: $alarm")
//停止振动
if (vibrationUtils.isVibrating) {
vibrationUtils.stopVibration()
}
//停止播放音乐
alarmPlayer?.release()
alarmPlayer = null
if (alarm.action == "start") {
//获取音量
val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
Log.d(TAG, "maxVolume=$maxVolume, currentVolume=$currentVolume")
//设置音量
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (maxVolume * alarm.volume / 100), 0)
//播放音乐
alarmPlayer = MediaPlayer().apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val audioAttributes = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build()
setAudioAttributes(audioAttributes)
} else {
// 对于 Android 5.0 之前的版本,使用 setAudioStreamType
val audioStreamType = AudioManager.STREAM_ALARM
setAudioStreamType(audioStreamType)
}
try {
if (alarm.music.isEmpty() || !File(alarm.music).exists()) {
val fd = resources.openRawResourceFd(R.raw.alarm)
setDataSource(fd.fileDescriptor, fd.startOffset, fd.length)
if (alarm.volume > 0) {
//获取音量
val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
Log.d(TAG, "maxVolume=$maxVolume, currentVolume=$currentVolume")
//设置音量
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (maxVolume * alarm.volume / 100), 0)
//播放音乐
alarmPlayer = MediaPlayer().apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val audioAttributes = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build()
setAudioAttributes(audioAttributes)
} else {
setDataSource(alarm.music)
// 对于 Android 5.0 之前的版本,使用 setAudioStreamType
val audioStreamType = AudioManager.STREAM_ALARM
setAudioStreamType(audioStreamType)
}
setOnPreparedListener {
Log.d(TAG, "MediaPlayer prepared")
start()
alarmPlayTimes++
//更新通知栏
updateNotification(alarm.description, R.drawable.auto_task_icon_alarm, true)
}
try {
if (alarm.music.isEmpty() || !File(alarm.music).exists()) {
val fd = resources.openRawResourceFd(R.raw.alarm)
setDataSource(fd.fileDescriptor, fd.startOffset, fd.length)
} else {
setDataSource(alarm.music)
}
setOnCompletionListener {
Log.d(TAG, "MediaPlayer completed")
if (alarm.playTimes == 0 || alarmPlayTimes < alarm.playTimes) {
setOnPreparedListener {
Log.d(TAG, "MediaPlayer prepared")
start()
alarmPlayTimes++
} else {
stop()
reset()
release()
alarmPlayer = null
alarmPlayTimes = 0
//恢复音量
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0)
//恢复通知栏
updateNotification(SettingUtils.notifyContent)
//更新通知栏
updateNotification(alarm.description, R.drawable.auto_task_icon_alarm, true)
}
}
setOnErrorListener { _, what, extra ->
Log.e(TAG, "MediaPlayer error: what=$what, extra=$extra")
release()
return@setOnErrorListener true
}
setOnCompletionListener {
Log.d(TAG, "MediaPlayer completed")
if (alarm.playTimes == 0 || alarmPlayTimes < alarm.playTimes) {
start()
alarmPlayTimes++
} else {
stop()
reset()
release()
alarmPlayer = null
alarmPlayTimes = 0
//恢复音量
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0)
//恢复通知栏
updateNotification(SettingUtils.notifyContent)
}
}
setVolume(alarm.volume / 100F, alarm.volume / 100F)
prepareAsync()
} catch (e: Exception) {
Log.e(TAG, "MediaPlayer Exception: ${e.message}")
setOnErrorListener { _, what, extra ->
Log.e(TAG, "MediaPlayer error: what=$what, extra=$extra")
release()
return@setOnErrorListener true
}
setVolume(alarm.volume / 100F, alarm.volume / 100F)
prepareAsync()
} catch (e: Exception) {
Log.e(TAG, "MediaPlayer Exception: ${e.message}")
}
}
}
//振动提醒
if (alarm.repeatTimes > 0) {
isVibrating = true
val patternString = alarm.vibrate.repeat(alarm.repeatTimes)
vibrationUtils.startVibration(patternString)
}
}
}
@ -176,7 +196,11 @@ class ForegroundService : Service() {
//纯客户端模式
if (SettingUtils.enablePureClientMode) return
//创建通知渠道
createNotificationChannel()
//初始化振动
vibrationUtils = VibrationUtils(this)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@ -292,6 +316,10 @@ class ForegroundService : Service() {
isRunning = false
alarmPlayer?.release()
alarmPlayer = null
//停止振动
if (vibrationUtils.isVibrating) {
vibrationUtils.stopVibration()
}
} catch (e: Exception) {
handleException(e, "stopForegroundService")
}

View File

@ -0,0 +1,77 @@
package com.idormy.sms.forwarder.utils
import android.content.Context
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.VibrationEffect
import android.os.Vibrator
@Suppress("DEPRECATION")
class VibrationUtils(context: Context) {
private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
private val handler = Handler(Looper.getMainLooper())
var isVibrating = false
private set
fun startVibration(patternString: String) {
isVibrating = true
val parsedPattern = parsePattern(patternString)
vibratePattern(parsedPattern, 0)
}
fun stopVibration() {
isVibrating = false
vibrator.cancel()
}
private fun parsePattern(pattern: String): List<Triple<Long, Boolean, Int>> {
val parsedPattern = mutableListOf<Triple<Long, Boolean, Int>>()
var currentChar = pattern[0]
var currentLength = 1L
for (i in 1 until pattern.length) {
if (pattern[i] == currentChar) {
currentLength++
} else {
parsedPattern.add(createTriple(currentChar, currentLength))
currentChar = pattern[i]
currentLength = 1L
}
}
parsedPattern.add(createTriple(currentChar, currentLength))
return parsedPattern
}
private fun createTriple(char: Char, length: Long): Triple<Long, Boolean, Int> {
val duration = 100L * length
val intensity = when (char) {
'=' -> 255
'-' -> 128
'_' -> 0
else -> 0
}
return Triple(duration, intensity > 0, intensity)
}
private fun vibratePattern(parsedPattern: List<Triple<Long, Boolean, Int>>, index: Int) {
if (isVibrating && index < parsedPattern.size) {
val (duration, shouldVibrate, intensity) = parsedPattern[index]
if (shouldVibrate) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val effect = VibrationEffect.createOneShot(duration, intensity)
vibrator.vibrate(effect)
} else {
vibrator.vibrate(duration)
}
}
handler.postDelayed({
if (isVibrating) {
vibrator.cancel()
vibratePattern(parsedPattern, index + 1)
}
}, duration)
}
}
}

View File

@ -86,101 +86,241 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/alarm_play_settings"
android:textStyle="bold"
tools:ignore="RelativeOverlap" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="5dp"
android:background="?attr/xui_config_color_separator_light" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alarm_volume"
android:textStyle="bold" />
<com.xuexiang.xui.widget.picker.XSeekBar
android:id="@+id/xsb_volume"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:padding="0dp"
app:xsb_max="100"
app:xsb_min="1" />
android:text="@string/alarm_play_settings"
android:textStyle="bold"
tools:ignore="RelativeOverlap" />
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_enable_music"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_alarm_settings_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alarm_play_times"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="5dp"
android:background="?attr/xui_config_color_separator_light" />
<com.xuexiang.xui.widget.picker.XSeekBar
android:id="@+id/xsb_loop_times"
android:layout_width="0dp"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:padding="0dp"
app:xsb_max="30"
app:xsb_min="0" />
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alarm_volume"
android:textStyle="bold" />
<com.xuexiang.xui.widget.picker.XSeekBar
android:id="@+id/xsb_volume"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:padding="0dp"
app:xsb_max="100"
app:xsb_min="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alarm_play_times"
android:textStyle="bold" />
<com.xuexiang.xui.widget.picker.XSeekBar
android:id="@+id/xsb_loop_times"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:padding="0dp"
app:xsb_max="30"
app:xsb_min="0" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alarm_music"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.ClearEditText
android:id="@+id/et_music_path"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:hint="@string/alarm_music_tips"
android:textSize="@dimen/text_size_mini"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/btn_file_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:gravity="center"
android:padding="5dp"
android:text="@string/select_file"
android:textColor="@color/white"
android:textSize="@dimen/text_size_mini"
app:sb_color_unpressed="@color/colorPrimary"
app:sb_ripple_color="@color/white"
app:sb_ripple_duration="500"
app:sb_shape_type="rectangle"
tools:ignore="SmallSp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_vibrate_settings"
style="@style/BarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alarm_music"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.ClearEditText
android:id="@+id/et_music_path"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:hint="@string/alarm_music_tips"
android:textSize="@dimen/text_size_mini"
tools:ignore="SmallSp" />
android:text="@string/alarm_vibrate_settings"
android:textStyle="bold"
tools:ignore="RelativeOverlap" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/btn_file_picker"
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_enable_vibrate"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_vibrate_settings_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="5dp"
android:background="?attr/xui_config_color_separator_light" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:gravity="center"
android:padding="5dp"
android:text="@string/select_file"
android:textColor="@color/white"
android:textSize="@dimen/text_size_mini"
app:sb_color_unpressed="@color/colorPrimary"
app:sb_ripple_color="@color/white"
app:sb_ripple_duration="500"
app:sb_shape_type="rectangle"
tools:ignore="SmallSp" />
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alarm_vibration_effect"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.ClearEditText
android:id="@+id/et_vibration_effect"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:hint="@string/alarm_vibration_effect_tips"
android:textSize="@dimen/text_size_mini"
tools:ignore="SmallSp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/bt_insert_vibration_effect_1"
style="@style/insertButtonStyle"
android:text="@string/alarm_vibration_effect_1" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/bt_insert_vibration_effect_2"
style="@style/insertButtonStyle"
android:layout_marginStart="5dp"
android:text="@string/alarm_vibration_effect_2" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/bt_insert_vibration_effect_3"
style="@style/insertButtonStyle"
android:layout_marginStart="5dp"
android:text="@string/alarm_vibration_effect_3" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alarm_repeat_times"
android:textStyle="bold" />
<com.xuexiang.xui.widget.picker.XSeekBar
android:id="@+id/xsb_repeat_times"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:padding="0dp"
app:xsb_max="30"
app:xsb_min="0" />
</LinearLayout>
</LinearLayout>

View File

@ -973,8 +973,8 @@
<string name="task_rule_tips">Control the enable/disable of "Rules"</string>
<string name="task_sender">Channels On/Off</string>
<string name="task_sender_tips">Control the enable/disable of "Senders"</string>
<string name="task_alarm">Alarm</string>
<string name="task_alarm_tips">Alarm</string>
<string name="task_alarm">Alarm Reminder</string>
<string name="task_alarm_tips">Play music/vibrate phone to remind</string>
<string name="task_resend">Resend Message</string>
<string name="task_resend_tips">Resend forwarded records since N hours ago, 0=ALL</string>
<string name="task_resend_desc" formatted="false">Resend forwarding records since %s hours ago for %s</string>
@ -1124,11 +1124,19 @@
<string name="start_alarm">Start Alarm</string>
<string name="stop_alarm">Stop Alarm</string>
<string name="alarm_play_settings">Playback Settings</string>
<string name="alarm_play_settings">Play Music</string>
<string name="alarm_music">Specify Music</string>
<string name="alarm_music_tips">Opt., download mp3/ogg/wav to the Download directory.</string>
<string name="alarm_volume">Alarm Volume</string>
<string name="alarm_play_times">Play Times(0=Infinite)</string>
<string name="alarm_vibrate_settings">Vibrate Phone</string>
<string name="alarm_repeat_times">Repeat Times</string>
<string name="alarm_vibration_effect">Vibration Effect</string>
<string name="alarm_vibration_effect_tips">Syntax: =[strong], -[weak], _[no], 100ms each</string>
<string name="alarm_vibration_effect_1">Strong vibration</string>
<string name="alarm_vibration_effect_2">Weak vibration</string>
<string name="alarm_vibration_effect_3">No vibration</string>
<string name="alarm_settings_error">At least one of Play Music/Vibrate Phone must be enabled</string>
<string name="invalid_tag" formatted="false">%s tag is invalid: %s</string>
<string name="invalid_task_name">Please input task name.</string>

View File

@ -974,8 +974,8 @@
<string name="task_rule_tips">控制【转发规则】的启用/禁用</string>
<string name="task_sender">启停通道</string>
<string name="task_sender_tips">控制【发送通道】的启用/禁用</string>
<string name="task_alarm">声音警报</string>
<string name="task_alarm_tips">声音警报</string>
<string name="task_alarm">警报提醒</string>
<string name="task_alarm_tips">播放音乐/振动手机提醒</string>
<string name="task_resend">重发消息</string>
<string name="task_resend_tips">自动重发N小时以来的转发记录0=全部</string>
<string name="task_resend_desc" formatted="false">自动重发%s小时以来%s的转发记录</string>
@ -1125,11 +1125,19 @@
<string name="start_alarm">启动警报</string>
<string name="stop_alarm">停止警报</string>
<string name="alarm_play_settings">播放设置</string>
<string name="alarm_play_settings">播放音乐</string>
<string name="alarm_music">指定音乐</string>
<string name="alarm_music_tips">可选,下载 mp3/ogg/wav 到 Download 目录</string>
<string name="alarm_volume">播放音量</string>
<string name="alarm_play_times">播放次数(0=无限)</string>
<string name="alarm_vibrate_settings">振动手机</string>
<string name="alarm_repeat_times">重复次数</string>
<string name="alarm_vibration_effect">振动效果</string>
<string name="alarm_vibration_effect_tips">语法:=[强振动], -[弱震动], _[不振动], 每次100ms</string>
<string name="alarm_vibration_effect_1">强振动 100ms</string>
<string name="alarm_vibration_effect_2">弱振动 100ms</string>
<string name="alarm_vibration_effect_3">不振动 100ms</string>
<string name="alarm_settings_error">播放音乐/振动手机必须至少开启一个</string>
<string name="invalid_tag" formatted="false">%s 标签无效:%s</string>
<string name="invalid_task_name">请输入任务名称</string>

View File

@ -967,8 +967,8 @@
<string name="task_rule_tips">控制【轉發規則】的啟用/禁用</string>
<string name="task_sender">啟停通道</string>
<string name="task_sender_tips">控制【發送通道】的啟用/禁用</string>
<string name="task_alarm">聲音警報</string>
<string name="task_alarm_tips">聲音警報</string>
<string name="task_alarm">警報提醒</string>
<string name="task_alarm_tips">播放音樂/震動手機發出提醒</string>
<string name="task_resend">重發消息</string>
<string name="task_resend_tips">自動重發N小時以來的轉發記錄0=全部</string>
<string name="task_resend_desc" formatted="false">自動重發%s小時以來%s的轉發記錄</string>
@ -1118,11 +1118,19 @@
<string name="start_alarm">啟動警報</string>
<string name="stop_alarm">停止警報</string>
<string name="alarm_play_settings">播放設置</string>
<string name="alarm_play_settings">播放音樂</string>
<string name="alarm_music">指定音樂</string>
<string name="alarm_music_tips">可選,下載 mp3/ogg/wav 到 Download 目錄</string>
<string name="alarm_volume">播放音量</string>
<string name="alarm_play_times">播放次數(0=無限)</string>
<string name="alarm_vibrate_settings">振動手機</string>
<string name="alarm_repeat_times">重複次數</string>
<string name="alarm_vibration_effect">振動效果</string>
<string name="alarm_vibration_effect_tips">語法:=[強振動], -[弱振動], _[不振動], 每次100ms</string>
<string name="alarm_vibration_effect_1">強振動 100ms</string>
<string name="alarm_vibration_effect_2">弱振動 100ms</string>
<string name="alarm_vibration_effect_3">不振動 100ms</string>
<string name="alarm_settings_error">播放音樂/振動手機必須至少開啟一個</string>
<string name="invalid_tag" formatted="false">%s 標籤無效:%s</string>
<string name="invalid_task_name">請輸入任務名稱</string>

View File

@ -1001,8 +1001,8 @@
<string name="task_rule_tips">控制【转发规则】的启用/禁用</string>
<string name="task_sender">启停通道</string>
<string name="task_sender_tips">控制【发送通道】的启用/禁用</string>
<string name="task_alarm">声音警报</string>
<string name="task_alarm_tips">播放音乐提醒</string>
<string name="task_alarm">警报提醒</string>
<string name="task_alarm_tips">播放音乐/振动手机提醒</string>
<string name="task_resend">重发消息</string>
<string name="task_resend_tips">自动重发N小时以来的转发记录0=全部</string>
<string name="task_resend_desc" formatted="false">自动重发%s小时以来%s的转发记录</string>
@ -1152,11 +1152,19 @@
<string name="start_alarm">启动警报</string>
<string name="stop_alarm">停止警报</string>
<string name="alarm_play_settings">播放设置</string>
<string name="alarm_play_settings">播放音乐</string>
<string name="alarm_music">指定音乐</string>
<string name="alarm_music_tips">可选,下载 mp3/ogg/wav 到 Download 目录</string>
<string name="alarm_volume">播放音量</string>
<string name="alarm_play_times">播放次数(0=无限)</string>
<string name="alarm_vibrate_settings">振动手机</string>
<string name="alarm_repeat_times">重复次数</string>
<string name="alarm_vibration_effect">振动效果</string>
<string name="alarm_vibration_effect_tips">语法:=[强振动], -[弱震动], _[不振动], 每次100ms</string>
<string name="alarm_vibration_effect_1">强振动 100ms</string>
<string name="alarm_vibration_effect_2">弱振动 100ms</string>
<string name="alarm_vibration_effect_3">不振动 100ms</string>
<string name="alarm_settings_error">播放音乐/振动手机必须至少开启一个</string>
<string name="invalid_tag" formatted="false">%s 标签无效:%s</string>
<string name="invalid_task_name">请输入任务名称</string>

View File

@ -13,7 +13,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# org.gradle.parallel=true
# 是否打包APK打正式包时请设置为true,使用正式的签名
isNeedPackage=true
isNeedClean=true
isNeedClean=false
# 是否排除Frpc动态库打正式包时请设置为true
excludeFrpclib=true
# 是否使用leakcanary检测内存泄漏打正式包时请设置为false