diff --git a/FCL/src/main/java/com/tungsten/fcl/ui/account/AccountUI.java b/FCL/src/main/java/com/tungsten/fcl/ui/account/AccountUI.java index 732200d7..d2128e78 100644 --- a/FCL/src/main/java/com/tungsten/fcl/ui/account/AccountUI.java +++ b/FCL/src/main/java/com/tungsten/fcl/ui/account/AccountUI.java @@ -18,7 +18,7 @@ public class AccountUI extends FCLCommonUI implements View.OnClickListener { private LinearLayoutCompat addOfflineAccount; private LinearLayoutCompat addMicrosoftAccount; - private LinearLayoutCompat addExternalAccount; + private LinearLayoutCompat addLoginServer; private ListView listView; private AccountListAdapter accountListAdapter; @@ -33,12 +33,15 @@ public class AccountUI extends FCLCommonUI implements View.OnClickListener { addOfflineAccount = findViewById(R.id.offline); addMicrosoftAccount = findViewById(R.id.microsoft); - addExternalAccount = findViewById(R.id.add_login_server); + addLoginServer = findViewById(R.id.add_login_server); addOfflineAccount.setOnClickListener(this); addMicrosoftAccount.setOnClickListener(this); - addExternalAccount.setOnClickListener(this); + addLoginServer.setOnClickListener(this); listView = findViewById(R.id.list); + + ListView serverListView = findViewById(R.id.server_list); + serverListView.setAdapter(new ServerListAdapter(getContext())); } @Override @@ -81,8 +84,8 @@ public class AccountUI extends FCLCommonUI implements View.OnClickListener { CreateAccountDialog dialog = new CreateAccountDialog(getContext(), Accounts.FACTORY_MICROSOFT); dialog.show(); } - if (view == addExternalAccount) { - CreateAccountDialog dialog = new CreateAccountDialog(getContext(), Accounts.FACTORY_AUTHLIB_INJECTOR); + if (view == addLoginServer) { + AddAuthlibInjectorServerDialog dialog = new AddAuthlibInjectorServerDialog(getContext()); dialog.show(); } } diff --git a/FCL/src/main/java/com/tungsten/fcl/ui/account/AddAuthlibInjectorServerDialog.java b/FCL/src/main/java/com/tungsten/fcl/ui/account/AddAuthlibInjectorServerDialog.java index babb1b30..857fcceb 100644 --- a/FCL/src/main/java/com/tungsten/fcl/ui/account/AddAuthlibInjectorServerDialog.java +++ b/FCL/src/main/java/com/tungsten/fcl/ui/account/AddAuthlibInjectorServerDialog.java @@ -1,14 +1,125 @@ package com.tungsten.fcl.ui.account; +import static com.tungsten.fcl.setting.ConfigHolder.config; +import static com.tungsten.fclcore.util.Logging.LOG; + import android.content.Context; +import android.view.View; +import android.widget.Toast; import androidx.annotation.NonNull; +import com.tungsten.fcl.R; +import com.tungsten.fclcore.auth.authlibinjector.AuthlibInjectorServer; +import com.tungsten.fclcore.task.Schedulers; +import com.tungsten.fclcore.task.Task; import com.tungsten.fcllibrary.component.dialog.FCLDialog; +import com.tungsten.fcllibrary.component.view.FCLButton; +import com.tungsten.fcllibrary.component.view.FCLConstraintLayout; +import com.tungsten.fcllibrary.component.view.FCLEditText; +import com.tungsten.fcllibrary.component.view.FCLTextView; -public class AddAuthlibInjectorServerDialog extends FCLDialog { +import java.io.IOException; +import java.util.Objects; +import java.util.logging.Level; + +import javax.net.ssl.SSLException; + +public class AddAuthlibInjectorServerDialog extends FCLDialog implements View.OnClickListener { + + private FCLConstraintLayout firstLayout; + private FCLConstraintLayout secondLayout; + + private FCLEditText editText; + private FCLTextView url; + private FCLTextView name; + + private FCLButton next; + private FCLButton back; + private FCLButton positive; + private FCLButton negativePri; + private FCLButton negativeSec; + + private AuthlibInjectorServer serverBeingAdded; public AddAuthlibInjectorServerDialog(@NonNull Context context) { super(context); + setCancelable(false); + setContentView(R.layout.dialog_add_authlib_injector_server); + + firstLayout = findViewById(R.id.first_layout); + secondLayout = findViewById(R.id.second_layout); + + editText = findViewById(R.id.url); + url = findViewById(R.id.address); + name = findViewById(R.id.name); + + next = findViewById(R.id.next); + back = findViewById(R.id.prev); + positive = findViewById(R.id.positive); + negativePri = findViewById(R.id.negative_pri); + negativeSec = findViewById(R.id.negative_sec); + + next.setOnClickListener(this); + back.setOnClickListener(this); + positive.setOnClickListener(this); + negativePri.setOnClickListener(this); + negativeSec.setOnClickListener(this); + + firstLayout.setVisibility(View.VISIBLE); + secondLayout.setVisibility(View.GONE); + } + + private void next() { + next.setEnabled(false); + negativePri.setEnabled(false); + String url = Objects.requireNonNull(editText.getText()).toString(); + Task.runAsync(() -> { + serverBeingAdded = AuthlibInjectorServer.locateServer(url); + }).whenComplete(Schedulers.androidUIThread(), exception -> { + next.setEnabled(true); + negativePri.setEnabled(true); + + if (exception == null) { + this.name.setText(serverBeingAdded.getName()); + this.url.setText(serverBeingAdded.getUrl()); + + firstLayout.setVisibility(View.GONE); + secondLayout.setVisibility(View.VISIBLE); + } else { + LOG.log(Level.WARNING, "Failed to resolve auth server: " + url, exception); + Toast.makeText(getContext(), resolveFetchExceptionMessage(exception), Toast.LENGTH_SHORT).show(); + } + }).start(); + } + + private String resolveFetchExceptionMessage(Throwable exception) { + if (exception instanceof SSLException) { + return getContext().getString(R.string.account_failed_ssl); + } else if (exception instanceof IOException) { + return getContext().getString(R.string.account_failed_connect_injector_server); + } else { + return exception.getClass().getName() + ": " + exception.getLocalizedMessage(); + } + } + + @Override + public void onClick(View v) { + if (v == next) { + next(); + } + if (v == back) { + firstLayout.setVisibility(View.VISIBLE); + secondLayout.setVisibility(View.GONE); + } + if (v == positive) { + if (!config().getAuthlibInjectorServers().contains(serverBeingAdded)) { + config().getAuthlibInjectorServers().add(serverBeingAdded); + } + dismiss(); + } + if (v == negativePri || v == negativeSec) { + dismiss(); + } } } diff --git a/FCL/src/main/java/com/tungsten/fcl/ui/account/CreateAccountDialog.java b/FCL/src/main/java/com/tungsten/fcl/ui/account/CreateAccountDialog.java index 1d9078da..a763926d 100644 --- a/FCL/src/main/java/com/tungsten/fcl/ui/account/CreateAccountDialog.java +++ b/FCL/src/main/java/com/tungsten/fcl/ui/account/CreateAccountDialog.java @@ -17,7 +17,7 @@ import android.widget.RelativeLayout; import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.appcompat.widget.AppCompatImageView; +import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; import com.google.android.material.tabs.TabLayout; @@ -34,6 +34,7 @@ import com.tungsten.fclcore.auth.CharacterSelector; import com.tungsten.fclcore.auth.NoSelectedCharacterException; import com.tungsten.fclcore.auth.authlibinjector.AuthlibInjectorAccountFactory; import com.tungsten.fclcore.auth.authlibinjector.AuthlibInjectorServer; +import com.tungsten.fclcore.auth.authlibinjector.BoundAuthlibInjectorAccountFactory; import com.tungsten.fclcore.auth.microsoft.MicrosoftAccountFactory; import com.tungsten.fclcore.auth.offline.OfflineAccountFactory; import com.tungsten.fclcore.auth.yggdrasil.GameProfile; @@ -50,6 +51,7 @@ import com.tungsten.fcllibrary.component.dialog.FCLDialog; import com.tungsten.fcllibrary.component.view.FCLButton; import com.tungsten.fcllibrary.component.view.FCLEditText; import com.tungsten.fcllibrary.component.view.FCLImageButton; +import com.tungsten.fcllibrary.component.view.FCLImageView; import com.tungsten.fcllibrary.component.view.FCLTabLayout; import com.tungsten.fcllibrary.component.view.FCLTextView; @@ -92,6 +94,10 @@ public class CreateAccountDialog extends FCLDialog implements View.OnClickListen init(factory); } + public CreateAccountDialog(@NonNull Context context, AuthlibInjectorServer authServer) { + this(context, Accounts.getAccountFactoryByAuthlibInjectorServer(authServer)); + } + private void init(AccountFactory factory) { if (factory == null) { showMethodSwitcher = true; @@ -114,8 +120,7 @@ public class CreateAccountDialog extends FCLDialog implements View.OnClickListen titleId = R.string.account_create_offline; } else if (factory instanceof MicrosoftAccountFactory) { titleId = R.string.account_create_microsoft; - } - else { + } else { titleId = R.string.account_create_external; } } @@ -136,6 +141,9 @@ public class CreateAccountDialog extends FCLDialog implements View.OnClickListen if (factory instanceof AuthlibInjectorAccountFactory) { details = new ExternalDetails(getContext()); } + if (factory instanceof BoundAuthlibInjectorAccountFactory) { + details = new ExternalDetails(getContext(), ((BoundAuthlibInjectorAccountFactory) factory).getServer()); + } detailsContainer.removeAllViews(); detailsContainer.addView(details.getView(), ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } @@ -323,7 +331,7 @@ public class CreateAccountDialog extends FCLDialog implements View.OnClickListen } } - private static class ExternalDetails implements Details, View.OnClickListener { + private static class ExternalDetails implements Details { private static final String[] ALLOWED_LINKS = { "homepage", "register" }; @@ -333,27 +341,31 @@ public class CreateAccountDialog extends FCLDialog implements View.OnClickListen private FCLTextView serverName; private FCLImageButton home; private FCLImageButton register; - private FCLImageButton setting; private final FCLEditText username; private final FCLEditText password; + @Nullable + private final AuthlibInjectorServer server; + public ExternalDetails(Context context) { + this(context, config().getAuthlibInjectorServers().size() == 0 ? null : config().getAuthlibInjectorServers().get(0)); + } + + public ExternalDetails(Context context, @Nullable AuthlibInjectorServer server) { this.context = context; this.view = LayoutInflater.from(context).inflate(R.layout.view_create_account_external, null); serverName = view.findViewById(R.id.server_name); home = view.findViewById(R.id.home); register = view.findViewById(R.id.register); - setting = view.findViewById(R.id.setting); username = view.findViewById(R.id.username); password = view.findViewById(R.id.password); - setting.setOnClickListener(this); - - refreshAuthenticateServer(config().getAuthlibInjectorServers().size() == 0 ? null : config().getAuthlibInjectorServers().get(0)); + this.server = server; + refreshAuthenticateServer(server); } - private void refreshAuthenticateServer(AuthlibInjectorServer authlibInjectorServer) { + public void refreshAuthenticateServer(AuthlibInjectorServer authlibInjectorServer) { if (authlibInjectorServer == null) { serverName.setText(context.getString(R.string.account_create_server_not_select)); home.setVisibility(View.GONE); @@ -403,20 +415,16 @@ public class CreateAccountDialog extends FCLDialog implements View.OnClickListen @Override public Object getAdditionalData() throws IllegalStateException { - return null; + if (server == null) { + throw new IllegalStateException(context.getString(R.string.account_create_server_not_select)); + } + return server; } @Override public View getView() throws IllegalStateException { return view; } - - @Override - public void onClick(View view) { - if (view == setting) { - - } - } } // character selector @@ -492,7 +500,7 @@ public class CreateAccountDialog extends FCLDialog implements View.OnClickListen static class ViewHolder { ConstraintLayout parent; - AppCompatImageView avatar; + FCLImageView avatar; FCLTextView name; } @@ -500,6 +508,16 @@ public class CreateAccountDialog extends FCLDialog implements View.OnClickListen void onSelect(GameProfile profile); } + @Override + public int getCount() { + return profiles.size(); + } + + @Override + public Object getItem(int i) { + return profiles.get(i); + } + @Override public View getView(int i, View view, ViewGroup viewGroup) { final ViewHolder viewHolder; @@ -515,10 +533,8 @@ public class CreateAccountDialog extends FCLDialog implements View.OnClickListen } GameProfile gameProfile = profiles.get(i); viewHolder.name.setText(gameProfile.getName()); - viewHolder.avatar.setBackground(TexturesLoader.avatarBinding(service, gameProfile.getId(), 32).get()); - viewHolder.parent.setOnClickListener(view1 -> { - listener.onSelect(gameProfile); - }); + viewHolder.avatar.imageProperty().bind(TexturesLoader.avatarBinding(service, gameProfile.getId(), 32)); + viewHolder.parent.setOnClickListener(view1 -> listener.onSelect(gameProfile)); return view; } } diff --git a/FCL/src/main/java/com/tungsten/fcl/ui/account/ServerListAdapter.java b/FCL/src/main/java/com/tungsten/fcl/ui/account/ServerListAdapter.java new file mode 100644 index 00000000..c26c7e8a --- /dev/null +++ b/FCL/src/main/java/com/tungsten/fcl/ui/account/ServerListAdapter.java @@ -0,0 +1,75 @@ +package com.tungsten.fcl.ui.account; + +import static com.tungsten.fcl.setting.ConfigHolder.config; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.constraintlayout.widget.ConstraintLayout; + +import com.tungsten.fcl.R; +import com.tungsten.fcl.setting.Accounts; +import com.tungsten.fclcore.auth.authlibinjector.AuthlibInjectorServer; +import com.tungsten.fclcore.fakefx.beans.InvalidationListener; +import com.tungsten.fclcore.fakefx.collections.ObservableList; +import com.tungsten.fcllibrary.component.FCLAdapter; +import com.tungsten.fcllibrary.component.view.FCLImageButton; +import com.tungsten.fcllibrary.component.view.FCLTextView; + +public class ServerListAdapter extends FCLAdapter { + + private final ObservableList list; + + public ServerListAdapter(Context context) { + super(context); + list = config().getAuthlibInjectorServers(); + + list.addListener((InvalidationListener) i -> notifyDataSetChanged()); + } + + static class ViewHolder { + ConstraintLayout parent; + FCLTextView name; + FCLTextView url; + FCLImageButton delete; + } + + @Override + public int getCount() { + return list.size(); + } + + @Override + public Object getItem(int i) { + return list.get(i); + } + + @SuppressLint("UseCompatLoadingForDrawables") + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + final ViewHolder viewHolder; + if (view == null) { + viewHolder = new ViewHolder(); + view = LayoutInflater.from(getContext()).inflate(R.layout.item_authlib_injector_server, null); + viewHolder.parent = view.findViewById(R.id.parent); + viewHolder.name = view.findViewById(R.id.name); + viewHolder.url = view.findViewById(R.id.url); + viewHolder.delete = view.findViewById(R.id.delete); + view.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) view.getTag(); + } + AuthlibInjectorServer server = list.get(i); + viewHolder.name.setText(server.getName()); + viewHolder.url.setText(server.getUrl()); + viewHolder.parent.setOnClickListener(v -> { + CreateAccountDialog dialog = new CreateAccountDialog(getContext(), server); + dialog.show(); + }); + viewHolder.delete.setOnClickListener(v -> config().getAuthlibInjectorServers().remove(server)); + return view; + } +} diff --git a/FCL/src/main/java/com/tungsten/fcl/ui/version/Versions.java b/FCL/src/main/java/com/tungsten/fcl/ui/version/Versions.java index d8398412..8bcafc7f 100644 --- a/FCL/src/main/java/com/tungsten/fcl/ui/version/Versions.java +++ b/FCL/src/main/java/com/tungsten/fcl/ui/version/Versions.java @@ -13,6 +13,7 @@ import com.tungsten.fcl.ui.account.CreateAccountDialog; import com.tungsten.fcl.util.RequestCodes; import com.tungsten.fcl.util.TaskCancellationAction; import com.tungsten.fclcore.auth.Account; +import com.tungsten.fclcore.auth.AccountFactory; import com.tungsten.fclcore.download.game.GameAssetDownloadTask; import com.tungsten.fclcore.game.GameDirectoryType; import com.tungsten.fclcore.task.Schedulers; @@ -197,7 +198,7 @@ public class Versions { private static void ensureSelectedAccount(Context context, Consumer action) { Account account = Accounts.getSelectedAccount(); if (account == null) { - CreateAccountDialog dialog = new CreateAccountDialog(context, null); + CreateAccountDialog dialog = new CreateAccountDialog(context, (AccountFactory) null); dialog.setOnDismissListener(dialogInterface -> { Account newAccount = Accounts.getSelectedAccount(); if (newAccount == null) { diff --git a/FCL/src/main/res/drawable/ic_baseline_server_24.xml b/FCL/src/main/res/drawable/ic_baseline_server_24.xml new file mode 100644 index 00000000..a3921716 --- /dev/null +++ b/FCL/src/main/res/drawable/ic_baseline_server_24.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/FCL/src/main/res/layout/dialog_add_authlib_injector_server.xml b/FCL/src/main/res/layout/dialog_add_authlib_injector_server.xml index 77d9ef65..99a25f32 100644 --- a/FCL/src/main/res/layout/dialog_add_authlib_injector_server.xml +++ b/FCL/src/main/res/layout/dialog_add_authlib_injector_server.xml @@ -1,6 +1,154 @@ + android:layout_width="400dp" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:padding="10dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FCL/src/main/res/layout/dialog_character_selector.xml b/FCL/src/main/res/layout/dialog_character_selector.xml index b334a8a4..c6324ddf 100644 --- a/FCL/src/main/res/layout/dialog_character_selector.xml +++ b/FCL/src/main/res/layout/dialog_character_selector.xml @@ -1,6 +1,6 @@ diff --git a/FCL/src/main/res/layout/item_account.xml b/FCL/src/main/res/layout/item_account.xml index 3a95c5cc..3a1d19fb 100644 --- a/FCL/src/main/res/layout/item_account.xml +++ b/FCL/src/main/res/layout/item_account.xml @@ -39,7 +39,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="10dp" - android:layout_marginEnd="130dp" + android:layout_marginEnd="170dp" app:layout_constraintStart_toEndOf="@+id/avatar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/FCL/src/main/res/layout/item_authlib_injector_server.xml b/FCL/src/main/res/layout/item_authlib_injector_server.xml new file mode 100644 index 00000000..d1d03df2 --- /dev/null +++ b/FCL/src/main/res/layout/item_authlib_injector_server.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FCL/src/main/res/layout/item_character.xml b/FCL/src/main/res/layout/item_character.xml index 24152955..28f8ffb4 100644 --- a/FCL/src/main/res/layout/item_character.xml +++ b/FCL/src/main/res/layout/item_character.xml @@ -6,14 +6,15 @@ - diff --git a/FCL/src/main/res/layout/view_create_account_external.xml b/FCL/src/main/res/layout/view_create_account_external.xml index 88916e51..ef6281c5 100644 --- a/FCL/src/main/res/layout/view_create_account_external.xml +++ b/FCL/src/main/res/layout/view_create_account_external.xml @@ -50,15 +50,6 @@ app:auto_tint="true" android:id="@+id/register"/> - - 关于 + 添加认证服务器 + 服务器地址 + 服务器名称 披风 创建账户 创建离线账户 @@ -39,6 +42,7 @@ 账户刷新失败 该账户中无角色。 无法连接认证服务器,请检查网络。 + 无法连接认证服务器。 请检查网络并确认你输入了正确的地址。 无法连接认证服务器。你可以暂时离线游戏或尝试重新登录。 无效的返回码,该认证服务器可能失效。 不正确的密码或被限速,请稍后再试。 @@ -47,6 +51,7 @@ 你的账户需要迁移至微软账户,如果已经迁移,请尝试重新登录。 无法下载 authlib-injector。 请检查网络,或修改下载源。 该角色已被删除。 + 连接服务器时发生了 SSL 错误。 登录了错误的账户。 无效的皮肤文件 你尚未满 18 岁,需要一位成年将你加入至家庭中。 @@ -146,6 +151,9 @@ 双击中心潜行 潜行键值 + 下一步 + 上一步 + 无法获取文件 %s。 无法校验文件。 缺少 SSL 证书。 diff --git a/FCL/src/main/res/values/strings.xml b/FCL/src/main/res/values/strings.xml index 75625ef4..ba20aadb 100644 --- a/FCL/src/main/res/values/strings.xml +++ b/FCL/src/main/res/values/strings.xml @@ -26,6 +26,9 @@ About + Add Authentication Server + Server URL + Server Name Cape Create Account Create Offline Account @@ -47,6 +50,7 @@ Account refresh failed There are no characters linked to this account. Unable to contact authentication servers, your Internet connection may be down. + Unable to connect to the authentication server. Please check your network and make sure you entered the correct URL. Cannot access authentication server. You can log in offline or try to re-login. Invalid server response, the authentication server may not be working. Incorrect password or rate limited, please try again later. @@ -55,6 +59,7 @@ Your account needs to be migrated to a Microsoft account. If you already did, you should re-login to your migrated Microsoft account instead. Unable to download authlib-injector. Please check your network, or try switching to a different download mirror. The character has already been deleted. + An SSL error occurred while connecting to the server. You have logged in to the wrong account. Invalid skin file Since you are not yet 18 years old, an adult must add you to a family in order for you to play Minecraft. @@ -157,6 +162,9 @@ Double Click Center to Sneak Sneak Keycode + Next + Prev + Unable to access the file %s. Cannot verify the integrity of the downloaded files. Unable to establish SSL connection due to missing SSL certificates in current Java installation.