Skip to content

Commit ed2f1d7

Browse files
angryLidbeasonxu
andauthored
feat: Implement QR Coder scanner (#639)
Co-authored-by: beasonxu <[email protected]>
1 parent bb3404a commit ed2f1d7

File tree

14 files changed

+115
-3
lines changed

14 files changed

+115
-3
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ dependencies {
2424
implementation(libs.androidx.coordinator)
2525
implementation(libs.androidx.recyclerview)
2626
implementation(libs.google.material)
27+
implementation(libs.quickie.bundled)
28+
implementation(libs.androidx.activity.ktx)
2729
}
2830

2931
tasks.getByName("clean", type = Delete::class) {

app/src/main/java/com/github/kr328/clash/NewProfileActivity.kt

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,35 @@ import android.content.Intent
66
import android.net.Uri
77
import android.provider.Settings
88
import androidx.activity.result.contract.ActivityResultContracts
9+
import androidx.lifecycle.lifecycleScope
910
import com.github.kr328.clash.common.constants.Intents
1011
import com.github.kr328.clash.common.util.intent
1112
import com.github.kr328.clash.common.util.setUUID
1213
import com.github.kr328.clash.design.NewProfileDesign
14+
import com.github.kr328.clash.design.R
1315
import com.github.kr328.clash.design.model.ProfileProvider
16+
import com.github.kr328.clash.design.util.showExceptionToast
1417
import com.github.kr328.clash.service.model.Profile
1518
import com.github.kr328.clash.util.withProfile
19+
import io.github.g00fy2.quickie.QRResult
20+
import io.github.g00fy2.quickie.QRResult.QRError
21+
import io.github.g00fy2.quickie.QRResult.QRMissingPermission
22+
import io.github.g00fy2.quickie.QRResult.QRSuccess
23+
import io.github.g00fy2.quickie.QRResult.QRUserCanceled
24+
import io.github.g00fy2.quickie.ScanQRCode
1625
import kotlinx.coroutines.Dispatchers
1726
import kotlinx.coroutines.isActive
27+
import kotlinx.coroutines.launch
1828
import kotlinx.coroutines.selects.select
1929
import kotlinx.coroutines.withContext
2030
import java.util.*
21-
import com.github.kr328.clash.design.R
2231

2332
class NewProfileActivity : BaseActivity<NewProfileDesign>() {
2433
private val self: NewProfileActivity
2534
get() = this
2635

36+
private val scanLauncher = registerForActivityResult(ScanQRCode(), ::scanResultHandler)
37+
2738
override suspend fun main() {
2839
val design = NewProfileDesign(this)
2940

@@ -45,8 +56,14 @@ class NewProfileActivity : BaseActivity<NewProfileDesign>() {
4556
val uuid: UUID? = when (val p = it.provider) {
4657
is ProfileProvider.File ->
4758
create(Profile.Type.File, name)
59+
4860
is ProfileProvider.Url ->
4961
create(Profile.Type.Url, name)
62+
63+
is ProfileProvider.QR -> {
64+
null
65+
}
66+
5067
is ProfileProvider.External -> {
5168
val data = p.get()
5269

@@ -68,9 +85,14 @@ class NewProfileActivity : BaseActivity<NewProfileDesign>() {
6885
launchProperties(uuid)
6986
}
7087
}
88+
7189
is NewProfileDesign.Request.OpenDetail -> {
7290
launchAppDetailed(it.provider)
7391
}
92+
93+
is NewProfileDesign.Request.LaunchScanner -> {
94+
scanLauncher.launch(null)
95+
}
7496
}
7597
}
7698
}
@@ -138,7 +160,41 @@ class NewProfileActivity : BaseActivity<NewProfileDesign>() {
138160
ProfileProvider.External(name.toString(), summary.toString(), icon, intent)
139161
}
140162

141-
listOf(ProfileProvider.File(self), ProfileProvider.Url(self)) + providers
163+
listOf(
164+
ProfileProvider.File(self),
165+
ProfileProvider.Url(self),
166+
ProfileProvider.QR(self)
167+
) + providers
142168
}
143169
}
170+
171+
private fun scanResultHandler(result: QRResult) {
172+
lifecycleScope.launch {
173+
when (result) {
174+
is QRSuccess -> {
175+
val url = result.content.rawValue
176+
?: result.content.rawBytes?.let { String(it) }.orEmpty()
177+
178+
createProfileByQrCode(url)
179+
}
180+
181+
QRUserCanceled -> {}
182+
QRMissingPermission -> design?.showExceptionToast(getString(R.string.import_from_qr_no_permission))
183+
is QRError -> design?.showExceptionToast(getString(R.string.import_from_qr_exception))
184+
}
185+
}
186+
}
187+
188+
private suspend fun createProfileByQrCode(url: String) {
189+
withProfile {
190+
launchProperties(
191+
create(
192+
type = Profile.Type.Url,
193+
name = getString(R.string.new_profile),
194+
url,
195+
)
196+
)
197+
}
198+
}
199+
144200
}

design/src/main/java/com/github/kr328/clash/design/NewProfileDesign.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class NewProfileDesign(context: Context) : Design<NewProfileDesign.Request>(cont
1111
sealed class Request {
1212
data class Create(val provider: ProfileProvider) : Request()
1313
data class OpenDetail(val provider: ProfileProvider.External) : Request()
14+
data class LaunchScanner(val provider: ProfileProvider.QR) : Request()
1415
}
1516

1617
private val binding = DesignNewProfileBinding
@@ -38,7 +39,12 @@ class NewProfileDesign(context: Context) : Design<NewProfileDesign.Request>(cont
3839
}
3940

4041
private fun requestCreate(provider: ProfileProvider) {
41-
requests.trySend(Request.Create(provider))
42+
if (provider is ProfileProvider.QR) {
43+
requests.trySend(Request.LaunchScanner(provider))
44+
} else {
45+
requests.trySend(Request.Create(provider))
46+
}
47+
4248
}
4349

4450
private fun requestDetail(provider: ProfileProvider): Boolean {

design/src/main/java/com/github/kr328/clash/design/model/ProfileProvider.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ sealed class ProfileProvider {
1414
get() = context.getString(R.string.import_from_file)
1515
override val icon: Drawable?
1616
get() = context.getDrawableCompat(R.drawable.ic_baseline_attach_file)
17+
18+
1719
}
1820

1921
class Url(private val context: Context) : ProfileProvider() {
@@ -25,6 +27,14 @@ sealed class ProfileProvider {
2527
get() = context.getDrawableCompat(R.drawable.ic_baseline_cloud_download)
2628
}
2729

30+
class QR(private val context: Context) : ProfileProvider() {
31+
override val name: String
32+
get() = context.getString(R.string.qr)
33+
override val summary: String
34+
get() = context.getString(R.string.import_from_qr)
35+
override val icon: Drawable?
36+
get() = context.getDrawableCompat(R.drawable.baseline_qr_code_scanner)
37+
}
2838
class External(
2939
override val name: String,
3040
override val summary: String,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:tint="?attr/colorControlNormal"
5+
android:viewportWidth="24"
6+
android:viewportHeight="24">
7+
<path android:fillColor="@android:color/white" android:pathData="M9.5,6.5v3h-3v-3H9.5M11,5H5v6h6V5L11,5zM9.5,14.5v3h-3v-3H9.5M11,13H5v6h6V13L11,13zM17.5,6.5v3h-3v-3H17.5M19,5h-6v6h6V5L19,5zM13,13h1.5v1.5H13V13zM14.5,14.5H16V16h-1.5V14.5zM16,13h1.5v1.5H16V13zM13,16h1.5v1.5H13V16zM14.5,17.5H16V19h-1.5V17.5zM16,16h1.5v1.5H16V16zM17.5,14.5H19V16h-1.5V14.5zM17.5,17.5H19V19h-1.5V17.5zM22,7h-2V4h-3V2h5V7zM22,22v-5h-2v3h-3v2H22zM2,22h5v-2H4v-3H2V22zM2,2v5h2V4h3V2H2z"/>
8+
9+
</vector>

design/src/main/res/values-ja-rJP/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<string name="import_from_file">ファイルからインポート</string>
3131
<string name="url">URL</string>
3232
<string name="import_from_url">URLからインポート</string>
33+
<string name="import_from_qr">QRコードからインポート</string>
3334
<string name="external">外部入力</string>
3435
<string name="format_type_unsaved">%s (未保存)</string>
3536
<string name="application_broken">アプリが破損しています</string>
@@ -250,4 +251,6 @@
250251
<string name="force_dns_mapping">Force DNS Mapping</string>
251252
<string name="parse_pure_ip">Parse Pure IP</string>
252253
<string name="override_destination">Override Destination</string>
254+
<string name="import_from_qr_no_permission">カメラのアクセスが制限されています。設定から有効にしてください。</string>
255+
<string name="import_from_qr_exception">システムで予期しない例外が発生しました。</string>
253256
</resources>

design/src/main/res/values-ko-rKR/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<string name="url">URL</string>
3232
<string name="import_from_url">URL에서 가져오기</string>
3333
<string name="external">외부</string>
34+
<string name="import_from_qr">QR코드에서 가져오기</string>
3435
<string name="format_type_unsaved">%s (저장되지 않음)</string>
3536
<string name="application_broken">앱 오류</string>
3637
<string name="application_crashed">앱 중지</string>
@@ -250,4 +251,6 @@
250251
<string name="force_dns_mapping">Force DNS Mapping</string>
251252
<string name="parse_pure_ip">Parse Pure IP</string>
252253
<string name="override_destination">Override Destination</string>
254+
<string name="import_from_qr_no_permission">카메라 접근이 제한되었습니다. 설정에서 허용해 주세요.</string>
255+
<string name="import_from_qr_exception">처리되지 않은 시스템 예외가 발생했습니다.</string>
253256
</resources>

design/src/main/res/values-ru/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<string name="url">URL</string>
3939
<string name="import_from_url">Импорт из URL</string>
4040
<string name="external">Внешний</string>
41+
<string name="import_from_qr">Импорт из QR-кода</string>
4142
<string name="format_type_unsaved">%s (не сохранён)</string>
4243

4344
<string name="application_broken">Приложение сломано</string>
@@ -314,4 +315,6 @@
314315
<string name="force_dns_mapping">Force DNS Mapping</string>
315316
<string name="parse_pure_ip">Parse Pure IP</string>
316317
<string name="override_destination">Override Destination</string>
318+
<string name="import_from_qr_no_permission">Доступ к камере ограничен. Разрешите его в настройках.</string>
319+
<string name="import_from_qr_exception">Произошла не обрабатываемая системная ошибка.</string>
317320
</resources>

design/src/main/res/values-vi/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,7 @@
236236
<string name="version_updated_tips">Các cài đặt đã được đặt lại và các cấu hình cũ cần được lưu lại.</string>
237237
<string name="vpn_service_options">Tuỳ chọn VpnService</string>
238238
<string name="warning">Cảnh báo</string>
239+
<string name="import_from_qr">Nhập từ Mã QR</string>
240+
<string name="import_from_qr_no_permission">Quyền truy cập camera bị hạn chế. Vui lòng bật trong Cài đặt.</string>
241+
<string name="import_from_qr_exception">Đã xảy ra ngoại lệ hệ thống không xử lý được.</string>
239242
</resources>

design/src/main/res/values-zh-rHK/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<string name="import_from_file">從文件導入</string>
4242
<string name="import_from_url">從 URL 導入</string>
4343
<string name="interface_">界面</string>
44+
<string name="import_from_qr">從二維碼導入</string>
4445
<string name="invalid_url">無效的 URL</string>
4546
<string name="logcat">Logcat</string>
4647
<string name="logs">日誌</string>
@@ -247,4 +248,6 @@
247248
<string name="force_dns_mapping">Force DNS Mapping</string>
248249
<string name="parse_pure_ip">Parse Pure IP</string>
249250
<string name="override_destination">Override Destination</string>
251+
<string name="import_from_qr_no_permission">相機權限受限,請前往設定開啟。</string>
252+
<string name="import_from_qr_exception">發生系統未知異常,操作失敗。</string>
250253
</resources>

0 commit comments

Comments
 (0)