Polish Android app UI

This commit is contained in:
2026-06-10 00:14:39 +09:00
parent ca09dcbd69
commit a98dc110e1

View File

@@ -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<Category> 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))