|
1 | 1 | package com.appcontrolx.ui |
2 | 2 |
|
3 | 3 | import android.content.Intent |
| 4 | +import android.content.pm.PackageManager |
4 | 5 | import android.net.Uri |
5 | 6 | import android.os.Build |
6 | 7 | import android.os.Bundle |
7 | 8 | import android.view.LayoutInflater |
8 | 9 | import android.view.View |
9 | 10 | import android.view.ViewGroup |
10 | 11 | import androidx.fragment.app.Fragment |
| 12 | +import androidx.lifecycle.lifecycleScope |
11 | 13 | import com.appcontrolx.R |
12 | 14 | import com.appcontrolx.databinding.FragmentAboutBinding |
13 | 15 | import com.appcontrolx.service.PermissionBridge |
| 16 | +import dagger.hilt.android.AndroidEntryPoint |
| 17 | +import kotlinx.coroutines.Dispatchers |
| 18 | +import kotlinx.coroutines.launch |
| 19 | +import kotlinx.coroutines.withContext |
| 20 | +import timber.log.Timber |
14 | 21 |
|
| 22 | +@AndroidEntryPoint |
15 | 23 | class AboutFragment : Fragment() { |
16 | 24 |
|
17 | 25 | private var _binding: FragmentAboutBinding? = null |
18 | | - private val binding get() = _binding!! |
| 26 | + private val binding get() = _binding |
19 | 27 |
|
20 | | - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { |
| 28 | + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
21 | 29 | _binding = FragmentAboutBinding.inflate(inflater, container, false) |
22 | | - return binding.root |
| 30 | + return _binding?.root |
23 | 31 | } |
24 | 32 |
|
25 | 33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
26 | 34 | super.onViewCreated(view, savedInstanceState) |
27 | 35 |
|
28 | 36 | setupAppInfo() |
29 | 37 | setupSystemInfo() |
| 38 | + setupStats() |
30 | 39 | setupLinks() |
31 | 40 | } |
32 | 41 |
|
33 | 42 | private fun setupAppInfo() { |
34 | | - val packageInfo = requireContext().packageManager |
35 | | - .getPackageInfo(requireContext().packageName, 0) |
36 | | - |
37 | | - binding.tvVersion.text = getString(R.string.about_version_format, |
38 | | - packageInfo.versionName, packageInfo.longVersionCode) |
| 43 | + val b = binding ?: return |
| 44 | + try { |
| 45 | + val packageInfo = requireContext().packageManager |
| 46 | + .getPackageInfo(requireContext().packageName, 0) |
| 47 | + |
| 48 | + b.tvVersion.text = getString(R.string.about_version_format, |
| 49 | + packageInfo.versionName, packageInfo.longVersionCode) |
| 50 | + } catch (e: Exception) { |
| 51 | + Timber.e(e, "Failed to get package info") |
| 52 | + } |
39 | 53 |
|
40 | 54 | // Current mode |
41 | | - val mode = PermissionBridge().detectMode() |
42 | | - binding.tvCurrentMode.text = mode.displayName() |
| 55 | + val mode = PermissionBridge(requireContext()).detectMode() |
| 56 | + b.tvCurrentMode.text = mode.displayName() |
43 | 57 | } |
44 | 58 |
|
45 | 59 | private fun setupSystemInfo() { |
46 | | - binding.tvDeviceInfo.text = getString(R.string.about_device_format, |
47 | | - Build.MANUFACTURER, Build.MODEL) |
48 | | - binding.tvAndroidVersion.text = getString(R.string.about_android_format, |
| 60 | + val b = binding ?: return |
| 61 | + b.tvDeviceInfo.text = getString(R.string.about_device_format, |
| 62 | + Build.MANUFACTURER.replaceFirstChar { it.uppercase() }, Build.MODEL) |
| 63 | + b.tvAndroidVersion.text = getString(R.string.about_android_format, |
49 | 64 | Build.VERSION.RELEASE, Build.VERSION.SDK_INT) |
50 | 65 | } |
51 | 66 |
|
| 67 | + private fun setupStats() { |
| 68 | + val b = binding ?: return |
| 69 | + |
| 70 | + lifecycleScope.launch { |
| 71 | + val (userApps, systemApps) = withContext(Dispatchers.IO) { |
| 72 | + val pm = requireContext().packageManager |
| 73 | + val packages = pm.getInstalledPackages(0) |
| 74 | + |
| 75 | + var user = 0 |
| 76 | + var system = 0 |
| 77 | + |
| 78 | + packages.forEach { pkg -> |
| 79 | + if ((pkg.applicationInfo.flags and android.content.pm.ApplicationInfo.FLAG_SYSTEM) != 0) { |
| 80 | + system++ |
| 81 | + } else { |
| 82 | + user++ |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + Pair(user, system) |
| 87 | + } |
| 88 | + |
| 89 | + b.tvUserAppsCount.text = userApps.toString() |
| 90 | + b.tvSystemAppsCount.text = systemApps.toString() |
| 91 | + |
| 92 | + // Actions count from prefs (placeholder for now) |
| 93 | + b.tvActionsCount.text = "0" |
| 94 | + } |
| 95 | + } |
| 96 | + |
52 | 97 | private fun setupLinks() { |
53 | | - binding.btnGithub.setOnClickListener { |
| 98 | + val b = binding ?: return |
| 99 | + |
| 100 | + b.btnGithub.setOnClickListener { |
54 | 101 | openUrl("https://github.com/risunCode/AppControl-X") |
55 | 102 | } |
56 | 103 |
|
57 | | - binding.btnShare.setOnClickListener { |
| 104 | + b.btnShare.setOnClickListener { |
58 | 105 | shareApp() |
59 | 106 | } |
60 | 107 |
|
61 | | - binding.btnRate.setOnClickListener { |
| 108 | + b.btnRate.setOnClickListener { |
62 | 109 | openUrl("https://github.com/risunCode/AppControl-X/stargazers") |
63 | 110 | } |
64 | 111 |
|
65 | | - binding.btnBugReport.setOnClickListener { |
| 112 | + b.btnBugReport.setOnClickListener { |
66 | 113 | openUrl("https://github.com/risunCode/AppControl-X/issues/new") |
67 | 114 | } |
68 | 115 | } |
69 | 116 |
|
70 | 117 | private fun openUrl(url: String) { |
71 | | - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) |
| 118 | + try { |
| 119 | + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) |
| 120 | + } catch (e: Exception) { |
| 121 | + Timber.e(e, "Failed to open URL: $url") |
| 122 | + } |
72 | 123 | } |
73 | 124 |
|
74 | 125 | private fun shareApp() { |
|
0 commit comments