From a98dc110e1d876b930f30877559399042a8120cd Mon Sep 17 00:00:00 2001 From: Wickedness Date: Wed, 10 Jun 2026 00:14:39 +0900 Subject: [PATCH] Polish Android app UI --- .../youtubefavoritesearch/MainActivity.java | 392 +++++++++++------- 1 file changed, 253 insertions(+), 139 deletions(-) diff --git a/app/src/main/java/com/example/youtubefavoritesearch/MainActivity.java b/app/src/main/java/com/example/youtubefavoritesearch/MainActivity.java index 205d52f..cc09cb0 100644 --- a/app/src/main/java/com/example/youtubefavoritesearch/MainActivity.java +++ b/app/src/main/java/com/example/youtubefavoritesearch/MainActivity.java @@ -12,7 +12,9 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Typeface; +import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.StateListDrawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -183,7 +185,11 @@ public class MainActivity extends Activity { LinearLayout header = new LinearLayout(this); header.setOrientation(LinearLayout.VERTICAL); - header.setPadding(dp(18), dp(18), dp(18), dp(12)); + header.setPadding(dp(20), dp(20), dp(20), dp(14)); + header.setBackgroundColor(palette.surface); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + header.setElevation(dp(1)); + } root.addView(header, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT @@ -208,30 +214,29 @@ public class MainActivity extends Activity { TextView title = new TextView(this); title.setText("유튜브 즐겨찾기 검색"); title.setTextColor(palette.textPrimary); - title.setTextSize(24); + title.setTextSize(22); title.setTypeface(Typeface.DEFAULT, Typeface.BOLD); + title.setIncludeFontPadding(false); titleColumn.addView(title); TextView guide = new TextView(this); guide.setTextColor(palette.textSecondary); guide.setTextSize(13); - guide.setText("탭으로 분류하고, 영상을 길게 눌러 여러 분류에 넣어두세요."); + guide.setText("저장한 YouTube 영상을 분류별로 빠르게 찾으세요."); + guide.setLineSpacing(0, 1.08f); LinearLayout.LayoutParams guideParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); - guideParams.topMargin = dp(4); + guideParams.topMargin = dp(6); titleColumn.addView(guide, guideParams); - Button themeButton = new Button(this); - themeButton.setText("테마"); - themeButton.setTextSize(13); - themeButton.setTextColor(Color.WHITE); - themeButton.setAllCaps(false); - themeButton.setBackground(buttonBackground(palette.accent)); + Button themeButton = createSmallButton("테마"); + themeButton.setTextColor(palette.accent); + themeButton.setBackground(softButtonBackground()); themeButton.setOnClickListener(view -> showThemeDialog()); - LinearLayout.LayoutParams themeButtonParams = new LinearLayout.LayoutParams(dp(76), dp(44)); - themeButtonParams.leftMargin = dp(10); + LinearLayout.LayoutParams themeButtonParams = new LinearLayout.LayoutParams(dp(72), dp(40)); + themeButtonParams.leftMargin = dp(12); titleRow.addView(themeButton, themeButtonParams); LinearLayout urlRow = new LinearLayout(this); @@ -241,7 +246,7 @@ public class MainActivity extends Activity { ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); - urlRowParams.topMargin = dp(14); + urlRowParams.topMargin = dp(16); header.addView(urlRow, urlRowParams); urlInput = new EditText(this); @@ -253,7 +258,7 @@ public class MainActivity extends Activity { urlInput.setTextColor(palette.textPrimary); urlInput.setHintTextColor(palette.textSecondary); urlInput.setBackground(inputBackground()); - urlInput.setPadding(dp(12), 0, dp(12), 0); + urlInput.setPadding(dp(14), 0, dp(14), 0); urlInput.setText(pendingUrl); urlInput.setOnEditorActionListener((view, actionId, event) -> { if (actionId == EditorInfo.IME_ACTION_DONE) { @@ -262,28 +267,23 @@ public class MainActivity extends Activity { } return false; }); - LinearLayout.LayoutParams urlInputParams = new LinearLayout.LayoutParams(0, dp(48), 1f); + LinearLayout.LayoutParams urlInputParams = new LinearLayout.LayoutParams(0, dp(50), 1f); urlRow.addView(urlInput, urlInputParams); - Button pasteButton = new Button(this); - pasteButton.setText("붙여넣기"); - pasteButton.setTextSize(13); + Button pasteButton = createSmallButton("붙여넣기"); pasteButton.setTextColor(palette.accent); - pasteButton.setAllCaps(false); - pasteButton.setBackground(inputBackground()); + pasteButton.setBackground(secondaryButtonBackground()); pasteButton.setOnClickListener(view -> pasteFromClipboard()); - LinearLayout.LayoutParams pasteParams = new LinearLayout.LayoutParams(dp(92), dp(48)); + LinearLayout.LayoutParams pasteParams = new LinearLayout.LayoutParams(dp(94), dp(50)); pasteParams.leftMargin = dp(8); urlRow.addView(pasteButton, pasteParams); - Button addButton = new Button(this); - addButton.setText("추가"); - addButton.setTextSize(13); + Button addButton = createSmallButton("추가"); addButton.setTextColor(Color.WHITE); - addButton.setAllCaps(false); + addButton.setTypeface(Typeface.DEFAULT, Typeface.BOLD); addButton.setBackground(buttonBackground(palette.accent)); addButton.setOnClickListener(view -> addVideoFromInput()); - LinearLayout.LayoutParams addParams = new LinearLayout.LayoutParams(dp(72), dp(48)); + LinearLayout.LayoutParams addParams = new LinearLayout.LayoutParams(dp(74), dp(50)); addParams.leftMargin = dp(8); urlRow.addView(addButton, addParams); @@ -291,11 +291,11 @@ public class MainActivity extends Activity { searchInput.setSingleLine(true); searchInput.setHint("검색어"); searchInput.setImeOptions(EditorInfo.IME_ACTION_SEARCH); - searchInput.setTextSize(16); + searchInput.setTextSize(15); searchInput.setTextColor(palette.textPrimary); searchInput.setHintTextColor(palette.textSecondary); searchInput.setBackground(inputBackground()); - searchInput.setPadding(dp(12), 0, dp(12), 0); + searchInput.setPadding(dp(14), 0, dp(14), 0); searchInput.setText(pendingSearch); searchInput.setOnEditorActionListener((view, actionId, event) -> { if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_DONE) { @@ -320,9 +320,9 @@ public class MainActivity extends Activity { }); LinearLayout.LayoutParams searchParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - dp(48) + dp(50) ); - searchParams.topMargin = dp(10); + searchParams.topMargin = dp(12); header.addView(searchInput, searchParams); addSearchHistoryChips(header); @@ -339,7 +339,7 @@ public class MainActivity extends Activity { listContainer = new LinearLayout(this); listContainer.setOrientation(LinearLayout.VERTICAL); - listContainer.setPadding(dp(14), dp(4), dp(14), dp(24)); + listContainer.setPadding(dp(16), dp(8), dp(16), dp(28)); scrollView.addView(listContainer, new ScrollView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT @@ -365,7 +365,7 @@ public class MainActivity extends Activity { ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); - scrollParams.topMargin = dp(8); + scrollParams.topMargin = dp(10); header.addView(scrollView, scrollParams); LinearLayout row = new LinearLayout(this); @@ -387,7 +387,7 @@ public class MainActivity extends Activity { }); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, - dp(34) + dp(32) ); params.rightMargin = dp(8); row.addView(chip, params); @@ -395,7 +395,7 @@ public class MainActivity extends Activity { Button clearButton = createSmallButton("지우기"); clearButton.setTextColor(palette.textSecondary); - clearButton.setBackground(inputBackground()); + clearButton.setBackground(secondaryButtonBackground()); clearButton.setOnClickListener(view -> { searchHistory.clear(); saveSearchHistory(); @@ -403,20 +403,27 @@ public class MainActivity extends Activity { }); row.addView(clearButton, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, - dp(34) + dp(32) )); } private void addControlBar(LinearLayout header) { - LinearLayout row = new LinearLayout(this); - row.setOrientation(LinearLayout.HORIZONTAL); - row.setGravity(Gravity.CENTER_VERTICAL); - LinearLayout.LayoutParams rowParams = new LinearLayout.LayoutParams( + HorizontalScrollView scrollView = new HorizontalScrollView(this); + scrollView.setHorizontalScrollBarEnabled(false); + LinearLayout.LayoutParams scrollParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); - rowParams.topMargin = dp(10); - header.addView(row, rowParams); + scrollParams.topMargin = dp(12); + header.addView(scrollView, scrollParams); + + LinearLayout row = new LinearLayout(this); + row.setOrientation(LinearLayout.HORIZONTAL); + row.setGravity(Gravity.CENTER_VERTICAL); + scrollView.addView(row, new HorizontalScrollView.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + dp(40) + )); if (selectionMode) { TextView countView = new TextView(this); @@ -424,16 +431,20 @@ public class MainActivity extends Activity { countView.setTextColor(palette.textPrimary); countView.setTextSize(14); countView.setTypeface(Typeface.DEFAULT, Typeface.BOLD); - row.addView(countView, new LinearLayout.LayoutParams(0, dp(40), 1f)); + countView.setGravity(Gravity.CENTER_VERTICAL); + countView.setIncludeFontPadding(false); + row.addView(countView, new LinearLayout.LayoutParams(dp(96), dp(38))); Button applyButton = createToolbarButton("분류"); applyButton.setTextColor(Color.WHITE); + applyButton.setTypeface(Typeface.DEFAULT, Typeface.BOLD); applyButton.setBackground(buttonBackground(palette.accent)); applyButton.setOnClickListener(view -> showBulkEditDialog()); row.addView(applyButton, toolbarButtonParams(false)); Button deleteButton = createToolbarButton("삭제"); deleteButton.setTextColor(Color.WHITE); + deleteButton.setTypeface(Typeface.DEFAULT, Typeface.BOLD); deleteButton.setBackground(buttonBackground(Color.rgb(214, 64, 64))); deleteButton.setOnClickListener(view -> confirmDeleteSelectedVideos()); row.addView(deleteButton, toolbarButtonParams(true)); @@ -454,7 +465,8 @@ public class MainActivity extends Activity { Button filterButton = createToolbarButton("분류 없음"); filterButton.setTextColor(uncategorizedOnly ? Color.WHITE : palette.textPrimary); - filterButton.setBackground(uncategorizedOnly ? buttonBackground(palette.accent) : inputBackground()); + filterButton.setTypeface(Typeface.DEFAULT, uncategorizedOnly ? Typeface.BOLD : Typeface.NORMAL); + filterButton.setBackground(uncategorizedOnly ? buttonBackground(palette.accent) : secondaryButtonBackground()); filterButton.setOnClickListener(view -> { uncategorizedOnly = !uncategorizedOnly; if (uncategorizedOnly) { @@ -481,17 +493,17 @@ public class MainActivity extends Activity { private Button createToolbarButton(String text) { Button button = createSmallButton(text); button.setTextColor(palette.textPrimary); - button.setBackground(inputBackground()); + button.setBackground(secondaryButtonBackground()); return button; } private LinearLayout.LayoutParams toolbarButtonParams(boolean withLeftMargin) { LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, - dp(40) + dp(38) ); if (withLeftMargin) { - params.leftMargin = dp(8); + params.leftMargin = dp(6); } return params; } @@ -503,7 +515,7 @@ public class MainActivity extends Activity { ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); - scrollParams.topMargin = dp(10); + scrollParams.topMargin = dp(12); header.addView(scrollView, scrollParams); LinearLayout row = new LinearLayout(this); @@ -536,7 +548,7 @@ public class MainActivity extends Activity { addButton.setContentDescription("분류 추가"); addButton.setBackground(buttonBackground(palette.accent)); addButton.setOnClickListener(view -> showCategoryDialog(null)); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(dp(44), dp(40)); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(dp(42), dp(38)); params.leftMargin = dp(8); row.addView(addButton, params); } @@ -544,6 +556,7 @@ public class MainActivity extends Activity { private Button createCategoryButton(String text, String categoryId, boolean selected, Category category) { Button button = createSmallButton(text); button.setTextColor(selected ? Color.WHITE : palette.textPrimary); + button.setTypeface(Typeface.DEFAULT, selected ? Typeface.BOLD : Typeface.NORMAL); button.setBackground(categoryTabBackground(selected)); button.setOnClickListener(view -> { selectedCategoryId = categoryId; @@ -561,7 +574,7 @@ public class MainActivity extends Activity { } LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, - dp(40) + dp(38) ); params.rightMargin = dp(8); button.setLayoutParams(params); @@ -574,11 +587,15 @@ public class MainActivity extends Activity { button.setTextSize(13); button.setAllCaps(false); button.setGravity(Gravity.CENTER); + button.setIncludeFontPadding(false); button.setMinWidth(0); button.setMinimumWidth(0); button.setMinHeight(0); button.setMinimumHeight(0); button.setPadding(dp(14), 0, dp(14), 0); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + button.setStateListAnimator(null); + } return button; } @@ -692,7 +709,7 @@ public class MainActivity extends Activity { } } - AlertDialog.Builder builder = new AlertDialog.Builder(this) + AlertDialog.Builder builder = dialogBuilder() .setTitle("이미 저장된 영상입니다") .setMessage(emptyFallback(video.title, video.url)) .setNegativeButton("취소", null) @@ -767,7 +784,7 @@ public class MainActivity extends Activity { ScrollView scrollView = new ScrollView(this); LinearLayout content = new LinearLayout(this); content.setOrientation(LinearLayout.VERTICAL); - content.setPadding(dp(20), dp(8), dp(20), 0); + content.setPadding(dp(20), dp(16), dp(20), dp(4)); scrollView.addView(content, new ScrollView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT @@ -787,16 +804,15 @@ public class MainActivity extends Activity { for (Category category : categories) { CheckBox checkBox = new CheckBox(this); checkBox.setText(category.name); - checkBox.setTextSize(15); checkBox.setChecked(preselectedCategoryIds.contains(category.id)); - checkBox.setPadding(0, dp(4), 0, dp(4)); checkBox.setTag(category.id); + styleCheckBox(checkBox); checks.add(checkBox); content.addView(checkBox); } } - new AlertDialog.Builder(this) + dialogBuilder() .setTitle("공유 영상 추가") .setView(scrollView) .setPositiveButton("저장", (dialogInterface, which) -> { @@ -945,12 +961,13 @@ public class MainActivity extends Activity { } private View createVideoCard(FavoriteVideo video) { + boolean selected = selectionMode && selectedVideoIds.contains(video.id); LinearLayout card = new LinearLayout(this); card.setOrientation(LinearLayout.HORIZONTAL); card.setGravity(Gravity.CENTER_VERTICAL); - card.setPadding(dp(12), dp(12), dp(12), dp(12)); - card.setBackground(cardBackground()); - card.setMinimumHeight(dp(112)); + card.setPadding(dp(14), dp(14), dp(14), dp(14)); + card.setBackground(cardBackground(selected)); + card.setMinimumHeight(dp(116)); card.setClickable(true); card.setOnClickListener(view -> { if (selectionMode) { @@ -965,35 +982,40 @@ public class MainActivity extends Activity { return true; }); } - card.setElevation(dp(1)); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + card.setElevation(selected ? dp(3) : dp(1)); + } LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); - cardParams.setMargins(0, dp(7), 0, dp(7)); + cardParams.setMargins(0, dp(6), 0, dp(6)); card.setLayoutParams(cardParams); if (selectionMode) { CheckBox selectBox = new CheckBox(this); selectBox.setChecked(selectedVideoIds.contains(video.id)); selectBox.setOnClickListener(view -> toggleSelectedVideo(video.id)); - LinearLayout.LayoutParams selectParams = new LinearLayout.LayoutParams(dp(42), dp(74)); + LinearLayout.LayoutParams selectParams = new LinearLayout.LayoutParams(dp(40), dp(72)); card.addView(selectBox, selectParams); } ImageView thumbnail = new ImageView(this); thumbnail.setScaleType(ImageView.ScaleType.CENTER_CROP); - thumbnail.setBackgroundColor(palette.thumbnailPlaceholder); + thumbnail.setBackground(roundedDrawable(palette.thumbnailPlaceholder, Color.TRANSPARENT, 0, 8)); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + thumbnail.setClipToOutline(true); + } thumbnail.setContentDescription(emptyFallback(video.title, "YouTube thumbnail")); thumbnail.setTag(video.id); - LinearLayout.LayoutParams thumbnailParams = new LinearLayout.LayoutParams(dp(132), dp(74)); + LinearLayout.LayoutParams thumbnailParams = new LinearLayout.LayoutParams(dp(128), dp(72)); card.addView(thumbnail, thumbnailParams); loadThumbnail(video, thumbnail); LinearLayout textColumn = new LinearLayout(this); textColumn.setOrientation(LinearLayout.VERTICAL); - textColumn.setPadding(dp(12), 0, 0, 0); + textColumn.setPadding(dp(14), 0, 0, 0); card.addView(textColumn, new LinearLayout.LayoutParams( 0, ViewGroup.LayoutParams.WRAP_CONTENT, @@ -1005,6 +1027,8 @@ public class MainActivity extends Activity { videoTitle.setTextColor(palette.textPrimary); videoTitle.setTextSize(15); videoTitle.setTypeface(Typeface.DEFAULT, Typeface.BOLD); + videoTitle.setIncludeFontPadding(false); + videoTitle.setLineSpacing(0, 1.06f); videoTitle.setMaxLines(2); videoTitle.setEllipsize(TextUtils.TruncateAt.END); textColumn.addView(videoTitle); @@ -1014,15 +1038,16 @@ public class MainActivity extends Activity { alias.setText(highlightSearchText(video.alias)); alias.setTextColor(palette.accent); alias.setTextSize(12); + alias.setIncludeFontPadding(false); alias.setSingleLine(true); alias.setEllipsize(TextUtils.TruncateAt.END); - alias.setPadding(dp(8), dp(3), dp(8), dp(3)); + alias.setPadding(dp(9), dp(4), dp(9), dp(4)); alias.setBackground(chipBackground()); LinearLayout.LayoutParams aliasParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ); - aliasParams.topMargin = dp(6); + aliasParams.topMargin = dp(8); textColumn.addView(alias, aliasParams); } @@ -1031,13 +1056,14 @@ public class MainActivity extends Activity { memoView.setText(highlightSearchText(video.memo)); memoView.setTextColor(palette.textSecondary); memoView.setTextSize(12); + memoView.setLineSpacing(0, 1.08f); memoView.setMaxLines(2); memoView.setEllipsize(TextUtils.TruncateAt.END); LinearLayout.LayoutParams memoParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); - memoParams.topMargin = dp(5); + memoParams.topMargin = dp(6); textColumn.addView(memoView, memoParams); } @@ -1047,13 +1073,14 @@ public class MainActivity extends Activity { categoryView.setText(highlightSearchText(categoryText)); categoryView.setTextColor(palette.accent); categoryView.setTextSize(12); + categoryView.setIncludeFontPadding(false); categoryView.setSingleLine(true); categoryView.setEllipsize(TextUtils.TruncateAt.END); LinearLayout.LayoutParams categoryParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); - categoryParams.topMargin = dp(5); + categoryParams.topMargin = dp(7); textColumn.addView(categoryView, categoryParams); } @@ -1063,29 +1090,30 @@ public class MainActivity extends Activity { subtitle.setText(highlightSearchText(details)); subtitle.setTextColor(palette.textSecondary); subtitle.setTextSize(12); + subtitle.setLineSpacing(0, 1.08f); subtitle.setMaxLines(2); subtitle.setEllipsize(TextUtils.TruncateAt.END); LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); - subtitleParams.topMargin = dp(5); + subtitleParams.topMargin = dp(6); textColumn.addView(subtitle, subtitleParams); } if (!selectionMode) { Button pinButton = createSmallButton(video.pinned ? "★" : "☆"); pinButton.setTextColor(video.pinned ? palette.accent : palette.textSecondary); - pinButton.setTextSize(20); + pinButton.setTextSize(19); pinButton.setContentDescription(video.pinned ? "고정 해제" : "상단 고정"); - pinButton.setBackgroundColor(Color.TRANSPARENT); + pinButton.setBackground(video.pinned ? softButtonBackground() : transparentTouchBackground()); pinButton.setOnClickListener(view -> { video.pinned = !video.pinned; saveVideos(); renderList(); }); - LinearLayout.LayoutParams pinParams = new LinearLayout.LayoutParams(dp(42), dp(74)); - pinParams.leftMargin = dp(6); + LinearLayout.LayoutParams pinParams = new LinearLayout.LayoutParams(dp(38), dp(72)); + pinParams.leftMargin = dp(8); card.addView(pinButton, pinParams); } @@ -1145,7 +1173,7 @@ public class MainActivity extends Activity { ScrollView scrollView = new ScrollView(this); LinearLayout content = new LinearLayout(this); content.setOrientation(LinearLayout.VERTICAL); - content.setPadding(dp(20), dp(8), dp(20), 0); + content.setPadding(dp(20), dp(16), dp(20), dp(4)); scrollView.addView(content, new ScrollView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT @@ -1162,6 +1190,7 @@ public class MainActivity extends Activity { aliasInput.setHint("예: 순두부, 자취요리, 저녁메뉴"); aliasInput.setSelectAllOnFocus(false); aliasInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE); + styleDialogInput(aliasInput, 46); aliasInput.setSelection(aliasInput.getText().length()); content.addView(aliasInput, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, @@ -1183,6 +1212,7 @@ public class MainActivity extends Activity { memoInput.setText(video.memo); memoInput.setHint("예: 3분 20초부터 보기"); memoInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE); + styleDialogInput(memoInput, 88); content.addView(memoInput, new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT @@ -1190,9 +1220,8 @@ public class MainActivity extends Activity { CheckBox pinnedCheckBox = new CheckBox(this); pinnedCheckBox.setText("상단에 고정"); - pinnedCheckBox.setTextSize(15); pinnedCheckBox.setChecked(video.pinned); - pinnedCheckBox.setPadding(0, dp(8), 0, dp(4)); + styleCheckBox(pinnedCheckBox); content.addView(pinnedCheckBox); Button refreshButton = createToolbarButton("제목 새로고침"); @@ -1245,16 +1274,15 @@ public class MainActivity extends Activity { checkBox.setText(suggestedCategoryIds.contains(category.id) ? category.name + " (추천)" : category.name); - checkBox.setTextSize(15); checkBox.setChecked(video.categoryIds.contains(category.id)); - checkBox.setPadding(0, dp(4), 0, dp(4)); checkBox.setTag(category.id); + styleCheckBox(checkBox); categoryChecks.add(checkBox); content.addView(checkBox); } } - AlertDialog dialog = new AlertDialog.Builder(this) + AlertDialog dialog = dialogBuilder() .setTitle("영상 편집") .setView(scrollView) .setPositiveButton("저장", (dialogInterface, which) -> { @@ -1279,7 +1307,7 @@ public class MainActivity extends Activity { } private void confirmDelete(FavoriteVideo video, AlertDialog editDialog) { - new AlertDialog.Builder(this) + dialogBuilder() .setTitle("삭제할까요?") .setMessage(emptyFallback(video.title, video.url)) .setPositiveButton("삭제", (dialogInterface, which) -> { @@ -1298,7 +1326,7 @@ public class MainActivity extends Activity { return; } - new AlertDialog.Builder(this) + dialogBuilder() .setTitle("선택한 영상을 삭제할까요?") .setMessage(String.format(Locale.ROOT, "%d개 영상이 삭제됩니다.", selectedVideoIds.size())) .setPositiveButton("삭제", (dialogInterface, which) -> { @@ -1324,9 +1352,9 @@ public class MainActivity extends Activity { input.setInputType(InputType.TYPE_CLASS_TEXT); input.setText(editing ? category.name : ""); input.setSelectAllOnFocus(true); - input.setPadding(dp(20), 0, dp(20), 0); + styleDialogInput(input, 48); - AlertDialog.Builder builder = new AlertDialog.Builder(this) + AlertDialog.Builder builder = dialogBuilder() .setTitle(editing ? "분류 수정" : "분류 추가") .setView(input) .setPositiveButton("저장", null) @@ -1365,7 +1393,7 @@ public class MainActivity extends Activity { } private void confirmDeleteCategory(Category category, AlertDialog editDialog) { - new AlertDialog.Builder(this) + dialogBuilder() .setTitle("분류를 삭제할까요?") .setMessage(category.name) .setPositiveButton("삭제", (dialogInterface, which) -> { @@ -1401,7 +1429,7 @@ public class MainActivity extends Activity { if (checked < SORT_LATEST || checked > SORT_RECENT) { checked = SORT_LATEST; } - new AlertDialog.Builder(this) + dialogBuilder() .setTitle("정렬") .setSingleChoiceItems(labels, checked, (dialogInterface, which) -> { sortMode = which; @@ -1428,7 +1456,7 @@ public class MainActivity extends Activity { private void showManageDialog() { String[] actions = {"분류 순서 변경", "전체 제목 새로고침", "백업 내보내기", "백업 가져오기"}; - new AlertDialog.Builder(this) + dialogBuilder() .setTitle("관리") .setItems(actions, (dialogInterface, which) -> { if (which == 0) { @@ -1453,10 +1481,10 @@ public class MainActivity extends Activity { ArrayList workingCategories = new ArrayList<>(categories); LinearLayout content = new LinearLayout(this); content.setOrientation(LinearLayout.VERTICAL); - content.setPadding(dp(16), dp(8), dp(16), 0); + content.setPadding(dp(16), dp(14), dp(16), dp(4)); renderReorderRows(content, workingCategories); - new AlertDialog.Builder(this) + dialogBuilder() .setTitle("분류 순서 변경") .setView(content) .setPositiveButton("저장", (dialogInterface, which) -> { @@ -1485,7 +1513,7 @@ public class MainActivity extends Activity { row.setTextSize(16); row.setGravity(Gravity.CENTER_VERTICAL); row.setPadding(dp(14), 0, dp(14), 0); - row.setBackground(cardBackground()); + row.setBackground(cardBackground(false)); row.setTag(category.id); row.setOnLongClickListener(view -> { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { @@ -1559,7 +1587,7 @@ public class MainActivity extends Activity { ScrollView scrollView = new ScrollView(this); LinearLayout content = new LinearLayout(this); content.setOrientation(LinearLayout.VERTICAL); - content.setPadding(dp(20), dp(8), dp(20), 0); + content.setPadding(dp(20), dp(16), dp(20), dp(4)); scrollView.addView(content, new ScrollView.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT @@ -1588,14 +1616,13 @@ public class MainActivity extends Activity { for (Category category : categories) { CheckBox checkBox = new CheckBox(this); checkBox.setText(category.name); - checkBox.setTextSize(15); - checkBox.setPadding(0, dp(4), 0, dp(4)); checkBox.setTag(category.id); + styleCheckBox(checkBox); checks.add(checkBox); content.addView(checkBox); } - new AlertDialog.Builder(this) + dialogBuilder() .setTitle("일괄 분류 편집") .setView(scrollView) .setPositiveButton("적용", (dialogInterface, which) -> { @@ -1639,7 +1666,7 @@ public class MainActivity extends Activity { LinearLayout content = new LinearLayout(this); content.setOrientation(LinearLayout.VERTICAL); int sidePadding = dp(20); - content.setPadding(sidePadding, dp(8), sidePadding, 0); + content.setPadding(sidePadding, dp(16), sidePadding, dp(4)); TextView modeLabel = dialogLabel("화면 모드"); content.addView(modeLabel); @@ -1677,7 +1704,7 @@ public class MainActivity extends Activity { colorRowParams.topMargin = dp(8); content.addView(colorRow, colorRowParams); - AlertDialog dialog = new AlertDialog.Builder(this) + AlertDialog dialog = dialogBuilder() .setTitle("테마") .setView(content) .setPositiveButton("닫기", null) @@ -1717,16 +1744,40 @@ public class MainActivity extends Activity { private TextView dialogLabel(String text) { TextView label = new TextView(this); label.setText(text); - label.setTextColor(Color.rgb(80, 87, 102)); + label.setTextColor(palette.textSecondary); label.setTextSize(13); label.setTypeface(Typeface.DEFAULT, Typeface.BOLD); + label.setIncludeFontPadding(false); return label; } + private AlertDialog.Builder dialogBuilder() { + int style = palette != null && palette.dark + ? android.R.style.Theme_Material_Dialog_Alert + : android.R.style.Theme_Material_Light_Dialog_Alert; + return new AlertDialog.Builder(this, style); + } + + private void styleDialogInput(EditText input, int minHeightDp) { + input.setTextSize(15); + input.setTextColor(palette.textPrimary); + input.setHintTextColor(palette.textSecondary); + input.setBackground(inputBackground()); + input.setPadding(dp(14), dp(8), dp(14), dp(8)); + input.setMinHeight(dp(minHeightDp)); + } + + private void styleCheckBox(CheckBox checkBox) { + checkBox.setTextColor(palette.textPrimary); + checkBox.setTextSize(15); + checkBox.setPadding(0, dp(5), 0, dp(5)); + } + private RadioButton themeRadioButton(String text, int id) { RadioButton button = new RadioButton(this); button.setText(text); button.setTextSize(15); + button.setTextColor(palette.textPrimary); button.setId(id); button.setPadding(0, dp(4), 0, dp(4)); return button; @@ -2080,42 +2131,77 @@ public class MainActivity extends Activity { return TextUtils.isEmpty(value) ? fallback : value; } - private GradientDrawable inputBackground() { - GradientDrawable drawable = new GradientDrawable(); - drawable.setColor(palette.input); - drawable.setCornerRadius(dp(8)); - drawable.setStroke(dp(1), palette.border); - return drawable; + private Drawable inputBackground() { + return statefulBackground( + roundedDrawable(palette.input, palette.border, 1, 8), + roundedDrawable(blend(palette.accent, palette.input, palette.dark ? 0.18f : 0.08f), + blend(palette.accent, palette.border, 0.38f), 1, 8) + ); } - private GradientDrawable buttonBackground(int color) { - GradientDrawable drawable = new GradientDrawable(); - drawable.setColor(color); - drawable.setCornerRadius(dp(8)); - return drawable; + private Drawable secondaryButtonBackground() { + return statefulBackground( + roundedDrawable(palette.input, palette.border, 1, 8), + roundedDrawable(blend(palette.accent, palette.input, palette.dark ? 0.16f : 0.07f), + blend(palette.accent, palette.border, 0.24f), 1, 8) + ); } - private GradientDrawable categoryTabBackground(boolean selected) { - GradientDrawable drawable = new GradientDrawable(); - drawable.setColor(selected ? palette.accent : palette.surface); - drawable.setCornerRadius(dp(8)); - drawable.setStroke(dp(1), selected ? palette.accent : palette.border); - return drawable; + private Drawable softButtonBackground() { + int fill = blend(palette.accent, palette.surface, palette.dark ? 0.24f : 0.11f); + int pressed = blend(palette.accent, palette.surface, palette.dark ? 0.34f : 0.18f); + int stroke = blend(palette.accent, palette.border, 0.32f); + return statefulBackground( + roundedDrawable(fill, stroke, 1, 8), + roundedDrawable(pressed, stroke, 1, 8) + ); } - private GradientDrawable cardBackground() { - GradientDrawable drawable = new GradientDrawable(); - drawable.setColor(palette.surface); - drawable.setCornerRadius(dp(8)); - drawable.setStroke(dp(1), palette.border); - return drawable; + private Drawable transparentTouchBackground() { + return statefulBackground( + roundedDrawable(Color.TRANSPARENT, Color.TRANSPARENT, 0, 8), + roundedDrawable(blend(palette.accent, palette.surface, palette.dark ? 0.20f : 0.10f), + Color.TRANSPARENT, 0, 8) + ); } - private GradientDrawable chipBackground() { - GradientDrawable drawable = new GradientDrawable(); - drawable.setColor(palette.chipBackground); - drawable.setCornerRadius(dp(8)); - return drawable; + private Drawable buttonBackground(int color) { + int pressed = palette.dark ? blend(Color.WHITE, color, 0.10f) : blend(Color.BLACK, color, 0.08f); + return statefulBackground( + roundedDrawable(color, Color.TRANSPARENT, 0, 8), + roundedDrawable(pressed, Color.TRANSPARENT, 0, 8) + ); + } + + private Drawable categoryTabBackground(boolean selected) { + if (selected) { + return buttonBackground(palette.accent); + } + return secondaryButtonBackground(); + } + + private Drawable cardBackground(boolean selected) { + if (selected) { + int fill = blend(palette.accent, palette.surface, palette.dark ? 0.22f : 0.10f); + return statefulBackground( + roundedDrawable(fill, palette.accent, 1, 8), + roundedDrawable(blend(palette.accent, palette.surface, palette.dark ? 0.30f : 0.15f), + palette.accent, 1, 8) + ); + } + return statefulBackground( + roundedDrawable(palette.surface, palette.border, 1, 8), + roundedDrawable(blend(palette.accent, palette.surface, palette.dark ? 0.10f : 0.04f), + palette.border, 1, 8) + ); + } + + private Drawable chipBackground() { + return statefulBackground( + roundedDrawable(palette.chipBackground, Color.TRANSPARENT, 0, 8), + roundedDrawable(blend(palette.accent, palette.chipBackground, palette.dark ? 0.18f : 0.10f), + Color.TRANSPARENT, 0, 8) + ); } private GradientDrawable swatchBackground(int color, boolean selected) { @@ -2127,6 +2213,34 @@ public class MainActivity extends Activity { return drawable; } + private GradientDrawable roundedDrawable(int fill, int stroke, int strokeWidthDp, int radiusDp) { + GradientDrawable drawable = new GradientDrawable(); + drawable.setColor(fill); + drawable.setCornerRadius(dp(radiusDp)); + if (strokeWidthDp > 0) { + drawable.setStroke(dp(strokeWidthDp), stroke); + } + return drawable; + } + + private StateListDrawable statefulBackground(Drawable normal, Drawable pressed) { + StateListDrawable drawable = new StateListDrawable(); + drawable.addState(new int[]{android.R.attr.state_pressed}, pressed); + drawable.addState(new int[]{android.R.attr.state_focused}, pressed); + drawable.addState(new int[]{}, normal); + return drawable; + } + + private int blend(int foreground, int background, float alpha) { + float clamped = Math.max(0f, Math.min(1f, alpha)); + float inverse = 1f - clamped; + return Color.rgb( + Math.round(Color.red(foreground) * clamped + Color.red(background) * inverse), + Math.round(Color.green(foreground) * clamped + Color.green(background) * inverse), + Math.round(Color.blue(foreground) * clamped + Color.blue(background) * inverse) + ); + } + private ThemePalette createPalette() { boolean dark = isDarkModeActive(); int accent = ACCENT_COLORS[Math.max(0, Math.min(accentIndex, ACCENT_COLORS.length - 1))]; @@ -2134,27 +2248,27 @@ public class MainActivity extends Activity { return new ThemePalette( true, accent, - Color.rgb(18, 20, 24), - Color.rgb(31, 34, 40), - Color.rgb(235, 238, 244), - Color.rgb(165, 174, 190), - Color.rgb(57, 63, 74), - Color.rgb(39, 43, 51), - Color.rgb(33, 44, 67), - Color.rgb(48, 54, 65) + Color.rgb(16, 18, 23), + Color.rgb(28, 32, 40), + Color.rgb(238, 242, 247), + Color.rgb(162, 171, 187), + Color.rgb(52, 59, 70), + Color.rgb(35, 40, 50), + Color.rgb(35, 45, 65), + Color.rgb(47, 54, 66) ); } return new ThemePalette( false, accent, - Color.rgb(246, 247, 250), + Color.rgb(243, 245, 248), Color.WHITE, - Color.rgb(31, 35, 44), - Color.rgb(94, 103, 118), - Color.rgb(217, 223, 235), - Color.WHITE, - Color.rgb(235, 241, 255), - Color.rgb(226, 231, 240) + Color.rgb(24, 32, 43), + Color.rgb(96, 106, 122), + Color.rgb(216, 224, 234), + Color.rgb(249, 251, 253), + Color.rgb(238, 244, 255), + Color.rgb(226, 232, 241) ); } @@ -2427,7 +2541,7 @@ public class MainActivity extends Activity { } String rawJson = readText(stream); BackupData backupData = parseBackup(rawJson); - new AlertDialog.Builder(this) + dialogBuilder() .setTitle("백업을 가져올까요?") .setMessage("현재 저장된 영상과 분류가 백업 내용으로 교체됩니다.") .setPositiveButton("가져오기", (dialogInterface, which) -> applyBackup(backupData))