Initial commit - v1.1.9

This commit is contained in:
2026-02-22 12:03:04 +09:00
commit 27339dc7b7
180 changed files with 12908 additions and 0 deletions

View File

@@ -0,0 +1,268 @@
package com.example.shiftalarm
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.fragment.app.Fragment
import com.example.shiftalarm.databinding.FragmentSettingsBasicBinding
import java.io.BufferedInputStream
import java.io.File
import java.io.FileOutputStream
import java.net.HttpURLConnection
import android.app.ProgressDialog
import android.net.Uri
import android.os.Build
import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import android.provider.Settings
import androidx.core.content.ContextCompat
class FragmentSettingsBasic : Fragment() {
private var _binding: FragmentSettingsBasicBinding? = null
private val binding get() = _binding!!
private val PREFS_NAME = "ShiftAlarmPrefs"
private var isUserInteraction = false
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentSettingsBasicBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
loadSettings()
setupListeners()
}
private fun loadSettings() {
val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
// Factory Spinner
setupFactorySpinner(prefs)
// Team Spinner
setupTeamSpinner(prefs)
// Version Info
try {
val pInfo = requireContext().packageManager.getPackageInfo(requireContext().packageName, 0)
binding.versionInfo.text = "Ver. ${pInfo.versionName} | 제작자: 산적이얌"
} catch (e: Exception) {
binding.versionInfo.text = "Ver. Unknown | 제작자: 산적이얌"
}
// Show/Hide Exact Alarm based on version
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
binding.btnExactAlarm.visibility = View.VISIBLE
binding.dividerExact.visibility = View.VISIBLE
} else {
binding.btnExactAlarm.visibility = View.GONE
binding.dividerExact.visibility = View.GONE
}
}
private fun setupFactorySpinner(prefs: android.content.SharedPreferences) {
val savedFactory = prefs.getString("selected_factory", "Jeonju")
val factoryIndex = if (savedFactory == "Nonsan") 1 else 0
binding.factorySpinner.setSelection(factoryIndex)
binding.factorySpinner.setOnTouchListener { _, _ ->
isUserInteraction = true
false
}
binding.factorySpinner.onItemSelectedListener = object : android.widget.AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: android.widget.AdapterView<*>?, view: View?, position: Int, id: Long) {
if (!isUserInteraction) return
val isNonsan = position == 1
val factory = if (isNonsan) "Nonsan" else "Jeonju"
val currentFactory = prefs.getString("selected_factory", "Jeonju")
if (factory == currentFactory) {
// Just update team spinner without resetting times if same factory
updateTeamSpinner(isNonsan)
return
}
// Save immediately
val editor = prefs.edit()
editor.putString("selected_factory", factory)
editor.apply()
// CRUCIAL: Re-sync all alarms for the new factory
lifecycleScope.launch {
syncAllAlarms(requireContext())
Toast.makeText(requireContext(), "공장 설정이 변경되었습니다.", Toast.LENGTH_SHORT).show()
}
// Update Team Spinner logic
updateTeamSpinner(isNonsan)
}
override fun onNothingSelected(parent: android.widget.AdapterView<*>?) {}
}
}
private fun updateTeamSpinner(isNonsan: Boolean) {
val currentSelection = binding.teamSpinner.selectedItemPosition
val teamOptions = if (isNonsan) {
arrayOf("A반", "B반", "C반")
} else {
arrayOf("A반", "B반", "C반", "D반")
}
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, teamOptions)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.teamSpinner.adapter = adapter
if (currentSelection < teamOptions.size) {
binding.teamSpinner.setSelection(currentSelection)
} else {
binding.teamSpinner.setSelection(0)
if (isUserInteraction) {
Toast.makeText(requireContext(), "논산 회사는 D반이 없습니다. A반으로 설정됩니다.", Toast.LENGTH_SHORT).show()
val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
prefs.edit().putString("selected_team", "A").apply()
}
}
}
private fun setupTeamSpinner(prefs: android.content.SharedPreferences) {
val savedFactory = prefs.getString("selected_factory", "Jeonju")
val isNonsan = savedFactory == "Nonsan"
updateTeamSpinner(isNonsan)
val savedTeam = prefs.getString("selected_team", "A")
val teamIndex = when (savedTeam) {
"A" -> 0
"B" -> 1
"C" -> 2
"D" -> if (isNonsan) 0 else 3
else -> 0
}
binding.teamSpinner.setSelection(teamIndex)
binding.teamSpinner.setOnTouchListener { _, _ ->
isUserInteraction = true
false
}
binding.teamSpinner.onItemSelectedListener = object : android.widget.AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: android.widget.AdapterView<*>?, view: View?, position: Int, id: Long) {
if (!isUserInteraction) return
val selectedTeam = when(position) {
0 -> "A"
1 -> "B"
2 -> "C"
3 -> "D"
else -> "A"
}
prefs.edit().putString("selected_team", selectedTeam).apply()
// CRUCIAL: Re-sync all alarms for the new team
lifecycleScope.launch {
syncAllAlarms(requireContext())
Toast.makeText(requireContext(), "${selectedTeam}반으로 알람이 재설정되었습니다.", Toast.LENGTH_SHORT).show()
}
}
override fun onNothingSelected(parent: android.widget.AdapterView<*>?) {}
}
}
override fun onResume() {
super.onResume()
updatePermissionStatuses()
}
private fun updatePermissionStatuses() {
val context = requireContext()
// 1. 배터리 (Battery)
val isBatteryIgnored = AlarmPermissionUtil.getBatteryOptimizationStatus(context)
binding.tvBatteryStatus.text = if (isBatteryIgnored) "[설정 완료: 절전 예외]" else "클릭하여 '제한 없음'으로 설정하세요"
binding.tvBatteryStatus.setTextColor(ContextCompat.getColor(context, if (isBatteryIgnored) R.color.primary else R.color.shift_red))
// 2. 정확한 알람 (Exact Alarm) - Android 12+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val isExactGranted = AlarmPermissionUtil.getExactAlarmStatus(context)
binding.tvExactStatus.text = if (isExactGranted) "[설정 완료: 정밀 알람]" else "필수: 클릭하여 권한을 허용하세요"
binding.tvExactStatus.setTextColor(ContextCompat.getColor(context, if (isExactGranted) R.color.primary else R.color.shift_red))
} else {
binding.btnExactAlarm.visibility = View.GONE
binding.dividerExact.visibility = View.GONE
}
// 3. 다른 앱 위에 표시 (Overlay)
val isOverlayGranted = AlarmPermissionUtil.getOverlayStatus(context)
binding.tvOverlayStatus.text = if (isOverlayGranted) "[설정 완료: 화면 우위]" else "필수: 알람창 노출을 위해 허용하세요"
binding.tvOverlayStatus.setTextColor(ContextCompat.getColor(context, if (isOverlayGranted) R.color.primary else R.color.shift_red))
// 4. 전체화면 알림 (Full Screen Intent) - Android 14+
if (Build.VERSION.SDK_INT >= 34) {
val isFullScreenGranted = AlarmPermissionUtil.getFullScreenIntentStatus(context)
binding.tvFullScreenStatus.text = if (isFullScreenGranted) "[설정 완료: 전체화면]" else "필수: 안드로이드 14 이상 필수 설정"
binding.tvFullScreenStatus.setTextColor(ContextCompat.getColor(context, if (isFullScreenGranted) R.color.primary else R.color.shift_red))
binding.btnFullScreenIntent.visibility = View.VISIBLE
} else {
binding.btnFullScreenIntent.visibility = View.GONE
}
}
private fun setupListeners() {
val prefs = requireContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
binding.btnBatteryOptimize.setOnClickListener {
AlarmPermissionUtil.requestBatteryOptimization(requireContext())
}
binding.btnExactAlarm.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply {
data = Uri.parse("package:${requireContext().packageName}")
}
startActivity(intent)
}
}
binding.btnOverlayPermission.setOnClickListener {
AlarmPermissionUtil.requestOverlayPermission(requireContext())
}
binding.btnFullScreenIntent.setOnClickListener {
AlarmPermissionUtil.requestFullScreenIntentPermission(requireContext())
}
binding.btnPermissionSettings.setOnClickListener {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.parse("package:${requireContext().packageName}")
}
startActivity(intent)
}
binding.btnCheckUpdate.setOnClickListener {
checkUpdate()
}
}
private fun checkUpdate() {
AppUpdateManager.checkUpdate(requireActivity(), silent = false)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}