列表,个人中心
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
package com.juhabi.birds.custom
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.PopupWindow
|
||||||
|
import com.juhabi.birds.R
|
||||||
|
|
||||||
|
/** 添加设备菜单,提供手动添加和扫一扫两个操作回调。 */
|
||||||
|
class AddDevicePopupWindow(context: Context) {
|
||||||
|
private val popupWindow: PopupWindow
|
||||||
|
|
||||||
|
init {
|
||||||
|
val content = LayoutInflater.from(context).inflate(R.layout.popup_add_device, null)
|
||||||
|
// 让按下态绿色能裁切到卡片圆角内
|
||||||
|
content.clipToOutline = true
|
||||||
|
popupWindow = PopupWindow(
|
||||||
|
content,
|
||||||
|
dp(context, 142),
|
||||||
|
dp(context, 84),
|
||||||
|
true
|
||||||
|
).apply {
|
||||||
|
isOutsideTouchable = true
|
||||||
|
elevation = dp(context, 6).toFloat()
|
||||||
|
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||||
|
}
|
||||||
|
content.findViewById<View>(R.id.action_manual_add).setOnClickListener {
|
||||||
|
popupWindow.dismiss()
|
||||||
|
manualAddAction?.invoke()
|
||||||
|
}
|
||||||
|
content.findViewById<View>(R.id.action_scan).setOnClickListener {
|
||||||
|
popupWindow.dismiss()
|
||||||
|
scanAction?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var manualAddAction: (() -> Unit)? = null
|
||||||
|
private var scanAction: (() -> Unit)? = null
|
||||||
|
|
||||||
|
/** 在指定锚点下方显示菜单,并设置两个操作的回调。 */
|
||||||
|
fun show(
|
||||||
|
anchor: View,
|
||||||
|
onManualAdd: () -> Unit,
|
||||||
|
onScan: () -> Unit
|
||||||
|
) {
|
||||||
|
manualAddAction = onManualAdd
|
||||||
|
scanAction = onScan
|
||||||
|
popupWindow.showAsDropDown(
|
||||||
|
anchor,
|
||||||
|
-dp(anchor.context, 104),
|
||||||
|
-dp(anchor.context, 4)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dp(context: Context, value: Int): Int =
|
||||||
|
(value * context.resources.displayMetrics.density).toInt()
|
||||||
|
}
|
||||||
7
app/src/main/res/color/popup_add_item_text.xml
Normal file
7
app/src/main/res/color/popup_add_item_text.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:color="@color/white"
|
||||||
|
android:state_pressed="true" />
|
||||||
|
<item android:color="@color/color_333333" />
|
||||||
|
</selector>
|
||||||
6
app/src/main/res/drawable/bg_confirm_dialog.xml
Normal file
6
app/src/main/res/drawable/bg_confirm_dialog.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@color/white" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
</shape>
|
||||||
5
app/src/main/res/drawable/bg_country_dialog.xml
Normal file
5
app/src/main/res/drawable/bg_country_dialog.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="@color/white" />
|
||||||
|
<corners android:radius="12dp" />
|
||||||
|
</shape>
|
||||||
6
app/src/main/res/drawable/bg_popup_add_device.xml
Normal file
6
app/src/main/res/drawable/bg_popup_add_device.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@color/white" />
|
||||||
|
<corners android:radius="12dp" />
|
||||||
|
</shape>
|
||||||
15
app/src/main/res/drawable/bg_popup_add_item.xml
Normal file
15
app/src/main/res/drawable/bg_popup_add_item.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- 按下瞬间变成主题绿 -->
|
||||||
|
<item android:state_pressed="true">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@color/color_zhu" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<!-- 未点选时保持白色 -->
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@color/white" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
7
app/src/main/res/drawable/icon_input_index.xml
Normal file
7
app/src/main/res/drawable/icon_input_index.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:drawable="@mipmap/icon_input_index_true"
|
||||||
|
android:state_pressed="true" />
|
||||||
|
<item android:drawable="@mipmap/icon_input_index_false" />
|
||||||
|
</selector>
|
||||||
7
app/src/main/res/drawable/icon_scan_index.xml
Normal file
7
app/src/main/res/drawable/icon_scan_index.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:drawable="@mipmap/icon_scan_index_true"
|
||||||
|
android:state_pressed="true" />
|
||||||
|
<item android:drawable="@mipmap/icon_scan_index_false" />
|
||||||
|
</selector>
|
||||||
26
app/src/main/res/layout/dialog_country_code.xml
Normal file
26
app/src/main/res/layout/dialog_country_code.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_country_dialog"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="18dp"
|
||||||
|
android:paddingBottom="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/select_country_code"
|
||||||
|
android:textColor="@color/color_2E2E2E"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/countryRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="420dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingBottom="8dp" />
|
||||||
|
</LinearLayout>
|
||||||
29
app/src/main/res/layout/item_country_code.xml
Normal file
29
app/src/main/res/layout/item_country_code.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="20dp"
|
||||||
|
android:paddingEnd="20dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/countryNameTextView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/color_2E2E2E"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/countryDialCodeTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:textColor="@color/color_999999"
|
||||||
|
android:textDirection="ltr"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
63
app/src/main/res/layout/popup_add_device.xml
Normal file
63
app/src/main/res/layout/popup_add_device.xml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_popup_add_device"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/action_manual_add"
|
||||||
|
android:layout_width="142dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:background="@drawable/bg_popup_add_item"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="22dp"
|
||||||
|
android:layout_height="22dp"
|
||||||
|
android:contentDescription="手动添加"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
android:src="@drawable/icon_input_index" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
android:text="手动添加"
|
||||||
|
android:textColor="@color/popup_add_item_text"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/action_scan"
|
||||||
|
android:layout_width="142dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:background="@drawable/bg_popup_add_item"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="22dp"
|
||||||
|
android:layout_height="22dp"
|
||||||
|
android:contentDescription="扫一扫"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
android:src="@drawable/icon_scan_index" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
android:text="扫一扫"
|
||||||
|
android:textColor="@color/popup_add_item_text"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
51
tools/GenerateCountryCallingCodeLocales.java
Normal file
51
tools/GenerateCountryCallingCodeLocales.java
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/** Regenerates localized country names while preserving the maintained dialing codes. */
|
||||||
|
public final class GenerateCountryCallingCodeLocales {
|
||||||
|
private static final Pattern ITEM = Pattern.compile(
|
||||||
|
"\\{\\\"code\\\":\\\"([^\\\"]+)\\\",\\\"name\\\":\\\"[^\\\"]*\\\",\\\"dial_code\\\":\\\"([^\\\"]+)\\\"}");
|
||||||
|
|
||||||
|
private record Country(String code, String dialCode) {}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
Path assets = Path.of("app", "src", "main", "assets");
|
||||||
|
String source = Files.readString(assets.resolve("country_calling_codes.json"), StandardCharsets.UTF_8);
|
||||||
|
Matcher matcher = ITEM.matcher(source);
|
||||||
|
List<Country> countries = new ArrayList<>();
|
||||||
|
while (matcher.find()) {
|
||||||
|
countries.add(new Country(matcher.group(1), matcher.group(2)));
|
||||||
|
}
|
||||||
|
if (countries.isEmpty()) {
|
||||||
|
throw new IllegalStateException("No country records found in the source JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
write(assets.resolve("country_calling_codes_en.json"), countries, Locale.ENGLISH);
|
||||||
|
write(assets.resolve("country_calling_codes_ar.json"), countries, Locale.forLanguageTag("ar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void write(Path output, List<Country> countries, Locale locale) throws IOException {
|
||||||
|
StringBuilder json = new StringBuilder("[\n");
|
||||||
|
for (int index = 0; index < countries.size(); index++) {
|
||||||
|
Country country = countries.get(index);
|
||||||
|
String name = new Locale.Builder().setRegion(country.code()).build().getDisplayCountry(locale);
|
||||||
|
json.append(" {\"code\":\"").append(country.code())
|
||||||
|
.append("\",\"name\":\"").append(escape(name))
|
||||||
|
.append("\",\"dial_code\":\"").append(country.dialCode()).append("\"}");
|
||||||
|
json.append(index + 1 == countries.size() ? "\n" : ",\n");
|
||||||
|
}
|
||||||
|
json.append("]\n");
|
||||||
|
Files.writeString(output, json, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String escape(String value) {
|
||||||
|
return value.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user