推分推荐

This commit is contained in:
2025-05-11 23:00:38 +08:00
parent 0649da7387
commit 633d9d4705
23 changed files with 1130 additions and 60 deletions

2
.idea/gradle.xml generated
View File

@@ -6,7 +6,6 @@
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" /> <option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
@@ -16,6 +15,5 @@
<option name="resolveExternalAnnotations" value="false" /> <option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
<option name="parallelModelFetch" value="true" />
</component> </component>
</project> </project>

9
.idea/misc.xml generated
View File

@@ -4,10 +4,17 @@
<component name="FrameworkDetectionExcludesConfiguration"> <component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" /> <file type="web" url="file://$PROJECT_DIR$" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">
<option name="id" value="Android" /> <option name="id" value="Android" />
</component> </component>
<component name="VisualizationToolProject">
<option name="state">
<ProjectState>
<option name="scale" value="0.2727272727272727" />
</ProjectState>
</option>
</component>
</project> </project>

View File

@@ -11,7 +11,7 @@ android {
minSdk 29 minSdk 29
targetSdk 34 targetSdk 34
versionCode 1 versionCode 1
versionName "1.6.3" versionName "1.6.4 beta"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@@ -119,12 +119,17 @@
android:theme="@style/Theme.FindMaimaiUltra.NoActionBar"> android:theme="@style/Theme.FindMaimaiUltra.NoActionBar">
</activity> </activity>
<activity <activity
android:name=".ui.LinkQQBot" android:name=".ui.login.LinkQQBot"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/Theme.FindMaimaiUltra.NoActionBar"> android:theme="@style/Theme.FindMaimaiUltra.NoActionBar">
</activity> </activity>
<activity android:name=".ui.WebsiteActivity"
android:exported="true"
android:screenOrientation="portrait"
android:label="@string/app_name"
android:theme="@style/Theme.FindMaimaiUltra.NoActionBar">
</activity>
<activity <activity
android:name=".ui.PaikaActivity" android:name=".ui.PaikaActivity"
android:exported="true" android:exported="true"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,142 @@
package org.astral.findmaimaiultra.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.signature.ObjectKey;
import jp.wasabeef.glide.transformations.BlurTransformation;
import org.astral.findmaimaiultra.R;
import org.astral.findmaimaiultra.been.faker.MusicRating;
import java.util.List;
public class SuggestMusicRatingAdapter extends RecyclerView.Adapter<SuggestMusicRatingAdapter.ViewHolder> {
private List<MusicRating> musicRatings;
private OnItemClickListener listener;
public interface OnItemClickListener {
void onItemClick(MusicRating musicRating);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
public SuggestMusicRatingAdapter(List<MusicRating> musicRatings) {
this.musicRatings = musicRatings;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_suggest_music_rating, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
MusicRating musicRating = musicRatings.get(position);
holder.musicName.setText(musicRating.getMusicName());
holder.level.setText("Level " + musicRating.getLevel_info() + " " + musicRating.getExtNum2());
String ac = String.valueOf(musicRating.getAchievement());
String target = String.valueOf(musicRating.getExtNum1());
//从后往前第5位加.
if(ac.length()>4) {
ac = ac.substring(0, ac.length() - 4) + "." + ac.substring(ac.length() - 4);
}
if(target.length()>4) {
target = target.substring(0, target.length() - 4) + "." + target.substring(target.length() - 4);
}
holder.ach.setText(ac + " -> " + target);
int id = musicRating.getMusicId();
if (id > 10000) {
id = id - 10000;
}
// 假设 musicRating 有一个方法 getCoverImageUrl() 返回图片的URL
String imageUrl = "https://assets2.lxns.net/maimai/jacket/" + id + ".png";
if (imageUrl != null) {
RequestOptions options = new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL) // 使用所有缓存策略
.signature(new ObjectKey(musicRating.getMusicId())); // 使用 MusicId 作为签名
Glide.with(holder.itemView.getContext())
.load(imageUrl)
.apply(options)
.into(holder.backgroundLayout);
}
// 根据 achievement 数据加载相应的图片
int achievement = musicRating.getExtNum1();
int achievementImageResId = getAchievementImageResId(achievement);
if (achievementImageResId != 0) {
Glide.with(holder.itemView.getContext())
.load(achievementImageResId)
.into(holder.achievementImage);
} else {
holder.achievementImage.setVisibility(View.GONE);
}
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(musicRating);
}
});
}
private int getAchievementImageResId(int achievement) {
// 根据 achievement 值返回相应的图片资源 ID
// 例如:
if(achievement > 1005000) {
return R.drawable.rank_sssp;
} else if (achievement > 1000000) {
return R.drawable.rank_sss;
} else if (achievement > 995000) {
return R.drawable.rank_ssp;
} else if (achievement > 990000) {
return R.drawable.rank_ss;
} else if (achievement > 980000) {
return R.drawable.rank_sp;
} else if (achievement > 970000) {
return R.drawable.rank_s;
} else if (achievement > 940000) {
return R.drawable.rank_aaa;
} else if (achievement > 900000) {
return R.drawable.rank_aaa;
} else if (achievement > 800000) {
return R.drawable.rank_a;
}
return R.drawable.rank_bbb;
}
@Override
public int getItemCount() {
return musicRatings.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView musicName;
TextView level;
TextView ach;
ImageView backgroundLayout;
ImageView achievementImage;
public ViewHolder(@NonNull View itemView) {
super(itemView);
musicName = itemView.findViewById(R.id.musicName);
level = itemView.findViewById(R.id.level);
ach = itemView.findViewById(R.id.ach);
backgroundLayout = itemView.findViewById(R.id.backgroundLayout);
achievementImage = itemView.findViewById(R.id.achievementImage);
}
}
}

View File

@@ -1,6 +1,8 @@
package org.astral.findmaimaiultra.been.lx; package org.astral.findmaimaiultra.been.lx;
import com.google.gson.annotations.SerializedName;
import java.util.Map; import java.util.Map;
public class Song { public class Song {
@@ -11,6 +13,8 @@ public class Song {
private int bpm; private int bpm;
private int version; private int version;
private String rights; private String rights;
@SerializedName("difficulties")
private Map<String, Difficulty[]> difficulties; private Map<String, Difficulty[]> difficulties;
// Getters and Setters // Getters and Setters

View File

@@ -229,6 +229,7 @@ public class MainActivity extends AppCompatActivity implements ImagePickerListen
updatePlace(); updatePlace();
return true; return true;
}); });
return false; return false;
} }

View File

@@ -0,0 +1,67 @@
package org.astral.findmaimaiultra.ui;
import android.annotation.SuppressLint;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.appcompat.app.AppCompatActivity;
import org.astral.findmaimaiultra.R;
public class WebsiteActivity extends AppCompatActivity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fullscreen_webview_dialog);
webView = findViewById(R.id.webView);
// 获取 SharedPreferences
SharedPreferences setting = getSharedPreferences("setting", MODE_PRIVATE);
String sessionId = setting.getString("sessionId", "");
// 启用 JavaScript
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
// 设置 WebViewClient 在页面加载完成后注入 JS
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// 注入 JavaScript 设置 localStorage.sessionId
String jsCode = "localStorage.setItem('sessionId', '" + sessionId + "');";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript(jsCode, null);
} else {
webView.loadUrl("javascript:" + jsCode);
}
}
});
// 从 Intent 获取要加载的 URL
String url = getIntent().getStringExtra("url");
if (url != null && !url.isEmpty()) {
webView.loadUrl(url);
} else {
finish(); // 如果没有 URL直接关闭 Activity
}
}
@Override
public void onBackPressed() {
if (webView.canGoBack()) {
webView.goBack();
} else {
super.onBackPressed();
}
}
}

View File

@@ -1,32 +1,43 @@
// HackGetUserId.java // HackGetUserId.java
package org.astral.findmaimaiultra.ui; package org.astral.findmaimaiultra.ui.login;
import android.Manifest; import android.Manifest;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.TableLayout; import android.widget.*;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import com.google.android.material.button.MaterialButton; import com.google.android.material.button.MaterialButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import okhttp3.*; import okhttp3.*;
import org.astral.findmaimaiultra.R; import org.astral.findmaimaiultra.R;
import org.astral.findmaimaiultra.been.faker.RegionData; import org.astral.findmaimaiultra.been.faker.RegionData;
import org.astral.findmaimaiultra.been.faker.UserData; import org.astral.findmaimaiultra.been.faker.UserData;
import org.astral.findmaimaiultra.been.faker.UserRegion; import org.astral.findmaimaiultra.been.faker.UserRegion;
import org.astral.findmaimaiultra.ui.MainActivity;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
@@ -64,11 +75,7 @@ public class LinkQQBot extends AppCompatActivity {
MaterialButton bangding = findViewById(R.id.bangding); MaterialButton bangding = findViewById(R.id.bangding);
bangding.setOnClickListener(v -> { bangding.setOnClickListener(v -> {
if (key.getText().toString().equals("")) { if (key.getText().toString().equals("")) {
Toast.makeText(this, "请输入基于QQ机器人获取的Key", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "请输入邮箱", Toast.LENGTH_SHORT).show();
return;
}
if (safecode.getText().toString().equals("")) {
Toast.makeText(this, "请输入您的安全码", Toast.LENGTH_SHORT).show();
return; return;
} }
try { try {
@@ -124,11 +131,65 @@ public class LinkQQBot extends AppCompatActivity {
}); });
} }
private void sendApiRequest(String key,String safecode,int code) throws Exception { private void getUserInfo() {
String url = "http://mai.godserver.cn:11451/api/qq/safeCoding?result=" + key + "&safecode=" + safecode; String url = "https://www.godserver.cn/cen/user/info";
SharedPreferences sharedPreferences = getSharedPreferences("setting", Context.MODE_PRIVATE);
String X_Session_ID = sharedPreferences.getString("sessionId", "");
Request request = new Request.Builder() Request request = new Request.Builder()
.url(url) .url(url)
.addHeader("X-Session-ID", X_Session_ID)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
if (response.isSuccessful()) {
String json = response.body().string();
User user = new Gson().fromJson(json, User.class);
if (user.getMai_userName().equals("")) {
runOnUiThread(() ->{
Snackbar.make(LinkQQBot.this.findViewById(android.R.id.content), "账号未绑定QQ机器人!请绑定(网站上也可以绑定)", Snackbar.LENGTH_LONG)
.setAction("绑定", v -> {
bindUser();
})
.show();
});
}
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("userId",user.getQqId());
editor.putString("userName",user.getMai_userName());
editor.putString("paikaname",user.getMai_userName());
editor.putString("https://mais.godserver.cn", user.getMai_userName());
editor.putInt("iconId",Integer.parseInt(user.getMai_avatarId()));
editor.apply();
try {
getUserRegionData(user.getQqId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
}
});
}
private void bindUser() {
}
private void sendApiRequest(String key,String safecode,int code) throws Exception {
String url = "https://www.godserver.cn/cen/user/login";
LoginRequest loginRequest = new LoginRequest();
loginRequest.setEmail(key);
loginRequest.setCodeOrPassword(safecode);
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create(MediaType.parse("application/json"), new Gson().toJson(loginRequest)))
.build(); .build();
Log.d("TAG", "sendApiRequest: " + url); Log.d("TAG", "sendApiRequest: " + url);
client.newCall(request).enqueue(new Callback() { client.newCall(request).enqueue(new Callback() {
@@ -142,28 +203,44 @@ public class LinkQQBot extends AppCompatActivity {
public void onResponse(Call call, Response response) throws IOException { public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) { if (response.isSuccessful()) {
final String responseData = response.body().string(); final String responseData = response.body().string();
SharedPreferences sharedPreferences = getSharedPreferences("setting", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
runOnUiThread(() -> { runOnUiThread(() -> {
Toast.makeText(LinkQQBot.this, "Response: " + responseData, Toast.LENGTH_LONG).show(); Message message = new Gson().fromJson(responseData, Message.class);
Log.d("TAG", "Response: " + responseData); if ("login".equals(message.getType())) {
if(responseData.equals("错误") && code==1) { if (message.getCode() == 200) {
try { // 登录成功
sendApiRequest(safecode,key,2); editor.remove("sessionId");
} catch (Exception e) { editor.putString("sessionId", message.getSessionId());
throw new RuntimeException(e);
}
return;
}
userId.setText(responseData);
SharedPreferences.Editor editor = sp.edit();
editor.putString("userId", responseData);
editor.apply(); editor.apply();
Toast.makeText(LinkQQBot.this, "设置已保存,您的UsrId已写入硬盘!", Toast.LENGTH_SHORT).show(); Log.d("TAG","成功!");
try { Snackbar.make(LinkQQBot.this.findViewById(android.R.id.content), "登录成功!", Snackbar.LENGTH_LONG)
getUserRegionData(responseData); .show();
getUserData(responseData); } else {
} catch (Exception e) { // 登录失败
throw new RuntimeException(e);
} }
} else if ("reg".equals(message.getType())) {
// 注册流程显示二维码
editor.remove("sessionId");
editor.putString("sessionId", message.getSessionId());
editor.apply();
Log.d("TAG","注册成功!");
// Base64 解码
String decodedKey = Arrays.toString(java.util.Base64.getDecoder().decode(message.getContent()));
// 构建 TOTP URI
String issuer = "ReisaPage - " + message.getContentType();
String totpUri = String.format("otpauth://totp/%s?secret=%s&issuer=%s",
Uri.encode(issuer),
Uri.encode(decodedKey),
Uri.encode(issuer));
showQRCodeDialog(totpUri);
}
getUserInfo();
}); });
} else { } else {
runOnUiThread(() -> Toast.makeText(LinkQQBot.this, "Request not successful", Toast.LENGTH_SHORT).show()); runOnUiThread(() -> Toast.makeText(LinkQQBot.this, "Request not successful", Toast.LENGTH_SHORT).show());
@@ -171,6 +248,48 @@ public class LinkQQBot extends AppCompatActivity {
} }
}); });
} }
private void showQRCodeDialog(String totpUri) {
try {
// 生成二维码图片
Bitmap qrCodeBitmap = encodeAsQRCode(totpUri, 500, 500);
// 创建 ImageView 并设置图片
ImageView imageView = new ImageView(this);
imageView.setImageBitmap(qrCodeBitmap);
// 构建 AlertDialog
new androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("请使用支持2FA的软件扫描二维码绑定账号,注意!这是你的唯一密码凭证!")
.setView(imageView)
.setPositiveButton("确定", (dialog, which) -> dialog.dismiss())
.show();
} catch (WriterException e) {
Toast.makeText(this, "生成二维码失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
private Bitmap encodeAsQRCode(String contents, int width, int height) throws WriterException {
BitMatrix result;
try {
result = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE, width, height, null);
} catch (IllegalArgumentException iae) {
return null;
}
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE;
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
private void getUserData(String userId) throws Exception { private void getUserData(String userId) throws Exception {
String url = "http://mai.godserver.cn:11451/api/qq/userData?qq=" + userId ; String url = "http://mai.godserver.cn:11451/api/qq/userData?qq=" + userId ;

View File

@@ -0,0 +1,22 @@
package org.astral.findmaimaiultra.ui.login;
public class LoginRequest {
private String email;
private String codeOrPassword;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getCodeOrPassword() {
return codeOrPassword;
}
public void setCodeOrPassword(String codeOrPassword) {
this.codeOrPassword = codeOrPassword;
}
}

View File

@@ -0,0 +1,58 @@
package org.astral.findmaimaiultra.ui.login;
public class Message {
private String type;
private String content;
private String contentType;
private String sessionId;
private int code;
private long timestamp;
public String getType() {
return type;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public void setType(String type) {
this.type = type;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}

View File

@@ -0,0 +1,85 @@
package org.astral.findmaimaiultra.ui.login;
public class User {
private String id;
private String name;
private String email;
private String twoFactor;
private String avatar;
private String lastLogin;
private String qqId;
private String mai_avatarId;
private String mai_userName;
public String getQqId() {
return qqId;
}
public void setQqId(String qqId) {
this.qqId = qqId;
}
public String getMai_avatarId() {
return mai_avatarId;
}
public void setMai_avatarId(String mai_avatarId) {
this.mai_avatarId = mai_avatarId;
}
public String getMai_userName() {
return mai_userName;
}
public void setMai_userName(String mai_userName) {
this.mai_userName = mai_userName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getTwoFactor() {
return twoFactor;
}
public void setTwoFactor(String twoFactor) {
this.twoFactor = twoFactor;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getLastLogin() {
return lastLogin;
}
public void setLastLogin(String lastLogin) {
this.lastLogin = lastLogin;
}
}

View File

@@ -1,13 +1,19 @@
package org.astral.findmaimaiultra.ui.music; package org.astral.findmaimaiultra.ui.music;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.os.Handler;
import android.view.View; import android.os.Looper;
import android.view.ViewGroup; import android.util.Log;
import android.view.*;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@@ -19,6 +25,7 @@ import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.gson.Gson; import com.google.gson.Gson;
@@ -26,19 +33,25 @@ import com.google.gson.reflect.TypeToken;
import okhttp3.*; import okhttp3.*;
import org.astral.findmaimaiultra.R; import org.astral.findmaimaiultra.R;
import org.astral.findmaimaiultra.adapter.MusicRatingAdapter; import org.astral.findmaimaiultra.adapter.MusicRatingAdapter;
import org.astral.findmaimaiultra.adapter.SuggestMusicRatingAdapter;
import org.astral.findmaimaiultra.been.faker.MaiUser; import org.astral.findmaimaiultra.been.faker.MaiUser;
import org.astral.findmaimaiultra.been.faker.MusicRating; import org.astral.findmaimaiultra.been.faker.MusicRating;
import org.astral.findmaimaiultra.been.faker.UserMusicList; import org.astral.findmaimaiultra.been.faker.UserMusicList;
import org.astral.findmaimaiultra.been.lx.Song;
import org.astral.findmaimaiultra.databinding.FragmentMusicBinding; import org.astral.findmaimaiultra.databinding.FragmentMusicBinding;
import org.astral.findmaimaiultra.ui.login.LinkQQBot;
import org.astral.findmaimaiultra.ui.MainActivity; import org.astral.findmaimaiultra.ui.MainActivity;
import org.astral.findmaimaiultra.utill.FileUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList; import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.*;
import java.util.Comparator; import java.util.concurrent.ExecutorService;
import java.util.List; import java.util.concurrent.Executors;
public class MusicFragment extends Fragment { public class MusicFragment extends Fragment {
private FragmentMusicBinding binding; private FragmentMusicBinding binding;
@@ -46,9 +59,14 @@ public class MusicFragment extends Fragment {
private SharedPreferences scorePrefs; private SharedPreferences scorePrefs;
private RecyclerView recyclerView; private RecyclerView recyclerView;
private MusicRatingAdapter adapter; private MusicRatingAdapter adapter;
private SuggestMusicRatingAdapter adapterSuggest;
private List<UserMusicList> musicSongsRatings; private List<UserMusicList> musicSongsRatings;
private List<MusicRating> musicRatings = new ArrayList<>(); private List<MusicRating> musicRatings = new ArrayList<>();
private String userId; private String userId;
private int iconId;
private String username;
private static Map<Integer, Song> songs = new HashMap<>();
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@@ -101,21 +119,184 @@ public class MusicFragment extends Fragment {
ViewGroup container, Bundle savedInstanceState) { ViewGroup container, Bundle savedInstanceState) {
MusicViewModel musicViewModel = MusicViewModel musicViewModel =
new ViewModelProvider(this).get(MusicViewModel.class); new ViewModelProvider(this).get(MusicViewModel.class);
SharedPreferences settingProperties = requireActivity().getSharedPreferences("setting", Context.MODE_PRIVATE);
username = settingProperties.getString("paikaname", "");
if (settingProperties.contains("userName")) {
username = settingProperties.getString("userName", "");
SharedPreferences.Editor editorM = setting.edit();
editorM.putString("paikaname",username);
editorM.commit();
}
iconId = settingProperties.getInt("iconId", 0);
binding = FragmentMusicBinding.inflate(inflater, container, false); binding = FragmentMusicBinding.inflate(inflater, container, false);
View root = binding.getRoot(); View root = binding.getRoot();
recyclerView = binding.getRoot().findViewById(R.id.recyclerView); recyclerView = binding.getRoot().findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2)); // 一行显示两个 recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2)); // 一行显示两个
if (setting.getString("image_uri", null) != null ) {
try {
File backgroundFile = FileUtils.getBackground(requireContext(), "background.jpg");
if (!backgroundFile.exists()) {
Toast.makeText(requireContext(), "文件不存在,请先设置背景图片", Toast.LENGTH_SHORT).show();
return root;
}
Bitmap bitmap = BitmapFactory.decodeFile(backgroundFile.getAbsolutePath());
if (bitmap != null) {
// 获取RecyclerView的尺寸
int recyclerViewWidth = 0;
int recyclerViewHeight = 0;
recyclerViewWidth = recyclerView.getWidth();
recyclerViewHeight = recyclerView.getHeight();
if (recyclerViewWidth > 0 && recyclerViewHeight > 0) {
// 计算缩放比例
float scaleWidth = ((float) recyclerViewWidth) / bitmap.getWidth();
float scaleHeight = ((float) recyclerViewHeight) / bitmap.getHeight();
// 选择较大的缩放比例以保持图片的原始比例
float scaleFactor = Math.max(scaleWidth, scaleHeight);
// 计算新的宽度和高度
int newWidth = (int) (bitmap.getWidth() * scaleFactor);
int newHeight = (int) (bitmap.getHeight() * scaleFactor);
// 缩放图片
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
// 计算裁剪区域
int x = (scaledBitmap.getWidth() - recyclerViewWidth) / 2;
int y = (scaledBitmap.getHeight() - recyclerViewHeight) / 2;
// 处理x和y为负数的情况
x = Math.max(x, 0);
y = Math.max(y, 0);
// 裁剪图片
Bitmap croppedBitmap = Bitmap.createBitmap(scaledBitmap, x, y, recyclerViewWidth, recyclerViewHeight);
// 创建一个新的 Bitmap与裁剪后的 Bitmap 大小相同
Bitmap transparentBitmap = Bitmap.createBitmap(croppedBitmap.getWidth(), croppedBitmap.getHeight(), croppedBitmap.getConfig());
// 创建一个 Canvas 对象,用于在新的 Bitmap 上绘制
Canvas canvas = new Canvas(transparentBitmap);
// 创建一个 Paint 对象,并设置透明度
Paint paint = new Paint();
paint.setAlpha(128); // 设置透明度为 50% (255 * 0.5 = 128)
// 将裁剪后的 Bitmap 绘制到新的 Bitmap 上,并应用透明度
canvas.drawBitmap(croppedBitmap, 0, 0, paint);
// 创建BitmapDrawable并设置其边界为RecyclerView的尺寸
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), transparentBitmap);
// 设置recyclerView的背景
recyclerView.setBackground(bitmapDrawable);
} else {
// 如果RecyclerView的尺寸未确定可以使用ViewTreeObserver来监听尺寸变化
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int recyclerViewWidth = 0;
int recyclerViewHeight = 0;
recyclerViewWidth = recyclerView.getWidth();
recyclerViewHeight = recyclerView.getHeight();
// 计算缩放比例
float scaleWidth = ((float) recyclerViewWidth) / bitmap.getWidth();
float scaleHeight = ((float) recyclerViewHeight) / bitmap.getHeight();
// 选择较大的缩放比例以保持图片的原始比例
float scaleFactor = Math.max(scaleWidth, scaleHeight);
// 计算新的宽度和高度
int newWidth = (int) (bitmap.getWidth() * scaleFactor);
int newHeight = (int) (bitmap.getHeight() * scaleFactor);
// 缩放图片
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
// 计算裁剪区域
int x = (scaledBitmap.getWidth() - recyclerViewWidth) / 2;
int y = (scaledBitmap.getHeight() - recyclerViewHeight) / 2;
// 处理x和y为负数的情况
x = Math.max(x, 0);
y = Math.max(y, 0);
// 裁剪图片
Bitmap croppedBitmap = Bitmap.createBitmap(scaledBitmap, x, y, recyclerViewWidth, recyclerViewHeight);
// 创建一个新的 Bitmap与裁剪后的 Bitmap 大小相同
Bitmap transparentBitmap = Bitmap.createBitmap(croppedBitmap.getWidth(), croppedBitmap.getHeight(), croppedBitmap.getConfig());
// 创建一个 Canvas 对象,用于在新的 Bitmap 上绘制
Canvas canvas = new Canvas(transparentBitmap);
// 创建一个 Paint 对象,并设置透明度
Paint paint = new Paint();
paint.setAlpha(128); // 设置透明度为 50% (255 * 0.5 = 128)
// 将裁剪后的 Bitmap 绘制到新的 Bitmap 上,并应用透明度
canvas.drawBitmap(croppedBitmap, 0, 0, paint);
// 创建BitmapDrawable并设置其边界为RecyclerView的尺寸
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), transparentBitmap);
recyclerView.setBackground(bitmapDrawable);
}
});
}
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(requireContext(), "图片加载失败,权限出错!", Toast.LENGTH_SHORT).show();
}
}
adapter = new MusicRatingAdapter(musicRatings); adapter = new MusicRatingAdapter(musicRatings);
adapter.setOnItemClickListener(musicRating -> { adapter.setOnItemClickListener(musicRating -> {
showMusicDetailDialog(musicRating); showMusicDetailDialog(musicRating);
}); });
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
FloatingActionButton f = binding.fab; FloatingActionButton f = binding.fab;
f.setOnClickListener(view -> { f.setOnClickListener(view -> {
showOptionsDialog(); showOptionsDialog();
}); });
ImageView user_avatar = binding.useravatar ;
Glide.with(this)
.load("https://assets2.lxns.net/maimai/icon/" + iconId +".png")
.into(user_avatar);
TextView user_name = binding.username;
user_name.setText(username);
if (!(iconId==0)){
MaterialButton login = binding.login;
login.setVisibility(View.GONE);
Intent loginIntent = new Intent(getActivity(), LinkQQBot.class);
login.setOnClickListener(view -> {
startActivity(loginIntent);
});
}else{
MaterialButton login = binding.login;
Intent loginIntent = new Intent(getActivity(), LinkQQBot.class);
login.setOnClickListener(view -> {
startActivity(loginIntent);
});
}
dataanlysis();
return root; return root;
} }
@@ -318,4 +499,227 @@ public class MusicFragment extends Fragment {
musicRatings.addAll(filteredList); musicRatings.addAll(filteredList);
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
} }
private void dataanlysis() {
ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper());
executor.execute(() -> {
// Perform data processing in the background
DataAnalyzer dataAnalyzer = new DataAnalyzer();
try {
InputStream inputStream = requireContext().getAssets().open("musicLike.json");
int size = inputStream.available();
byte[] buffer = new byte[size];
inputStream.read(buffer);
inputStream.close();
String json = new String(buffer, StandardCharsets.UTF_8);
dataAnalyzer = new Gson().fromJson(json,DataAnalyzer.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
InputStream inputStream = requireContext().getAssets().open("songs_cache.json");
int size = inputStream.available();
byte[] buffer = new byte[size];
inputStream.read(buffer);
inputStream.close();
String json = new String(buffer, StandardCharsets.UTF_8);
Type type = new TypeToken<Map<String, Song>>() {}.getType();
Map<String, Song> loadedSongs = new Gson().fromJson(json, type);
// 手动转换 key 为 Integer 并放入全局 map
for (Map.Entry<String, Song> entry : loadedSongs.entrySet()) {
Integer id = Integer.parseInt(entry.getKey());
songs.put(id, entry.getValue());
songs.put(id + 10000, entry.getValue()); // 如果你需要 standard 版本
}
} catch (IOException e) {
throw new RuntimeException(e);
}
Collections.sort(musicRatings, (o1, o2) -> Integer.compare(o2.getRating(), o1.getRating()));
int total = 0;
List<MusicRating> b50 = musicRatings.subList(0, 50);
List<MusicRating> b100 = musicRatings.subList(50, 100);
List<Integer> ids= new ArrayList<>();
int worstRating = b50.get(b50.size() - 1).getRating();
List<MusicRating> suggestMusicRatingList = new ArrayList<>();
for (int x = 0; x < 50; x++) {
total += b50.get(x).getRating();
MusicRating m = b100.get(x);
int nowache = m.getAchievement();
int target = 0;
if (nowache >= 1000000 && nowache < 10050000) {
target = 1005001;
} else if (nowache >= 995000 && nowache < 10000000) {
target = 1000001;
} else if (nowache >= 990000 && nowache < 995000) {
target = 995001;
} else if (nowache >= 980000 && nowache < 990000) {
target = 990001;
} else if (nowache >= 970000 && nowache < 980000) {
target = 980001;
}
double b1 = (double) target / 10000;
int targetRating = getRatingChart(m.getLevel_info(), b1);
if (targetRating > worstRating) {
m.setExtNum1(target);
if (m.getMusicId() > 10000) {
ids.add(m.getMusicId()-10000);
}else {
ids.add(m.getMusicId());
}
m.setExtNum2(targetRating);
suggestMusicRatingList.add(m);
}
}
List<EasySong> easySongs = new ArrayList<>();
//看看别人打什么
Log.d("TOP",total + "");
if (total>=16000) {
easySongs = dataAnalyzer.getEasySongs().get("16000");
}else if (total>=15500) {
easySongs = dataAnalyzer.getEasySongs().get("15500");
}else if (total>=15000) {
easySongs = dataAnalyzer.getEasySongs().get("15000");
}else if (total>=14500) {
easySongs = dataAnalyzer.getEasySongs().get("14500");
}else if (total>=14000) {
easySongs = dataAnalyzer.getEasySongs().get("14000");
}else if (total>=13000) {
easySongs = dataAnalyzer.getEasySongs().get("13000");
}else if (total>=12000) {
easySongs = dataAnalyzer.getEasySongs().get("12000");
}else if (total>=11000) {
easySongs = dataAnalyzer.getEasySongs().get("11000");
}
for (int x = 0 ; x < easySongs.size();x ++) {
EasySong e = easySongs.get(x);
if (e.getPercent()>0.1) {
if (ids.contains(e.getId())) {
continue;
}
;
double diff = songs.get(e.getId()).getDifficulties().get(e.getType())[e.getLevel()].getLevel_value();
double b = 99.0000;
for (int i = 0; i < 3; i++) {
b = b + 0.5 * (i - 1);
int ra = getRatingChart(diff, b);
if (ra > worstRating) {
MusicRating musicRating = new MusicRating();
musicRating.setMusicId(e.getId());
musicRating.setMusicName(e.getTitle());
musicRating.setExtNum1((int)(b*10000));
musicRating.setExtNum2(ra);
musicRating.setRating(0);
musicRating.setAchievement(0);
musicRating.setLevel_info(diff);
musicRating.setType(e.getType());
suggestMusicRatingList.add(musicRating);
break;
}
}
}
}
// Update UI on the main thread
handler.post(() -> {
RecyclerView suggest = binding.getRoot().findViewById(R.id.suggestion);
suggest.setLayoutManager(new GridLayoutManager(getContext(), 1));
adapterSuggest = new SuggestMusicRatingAdapter(suggestMusicRatingList);
suggest.setAdapter(adapterSuggest);
adapterSuggest.notifyDataSetChanged();
});
});
}
public int getRatingChart(double a1, double b1) {
double sys = 22.4;
if (b1 >= 100.5000) {
return (int) (a1 * 22.512);
}
if (b1 == 100.4999) {
sys = 22.2;
} else if (b1 >= 100.0000) {
sys = 21.6;
} else if (b1 == 99.9999) {
sys = 21.4;
} else if (b1 >= 99.5000) {
sys = 21.1;
} else if (b1 >= 99.0000) {
sys = 20.8;
} else if (b1 >= 98.0000) {
sys = 20.3;
} else if (b1 >= 97.0000) {
sys = 20.0;
} else {
sys = 0;
}
return (int) (a1 * sys * b1 / 100);
}
}
class DataAnalyzer {
Map<String, List<EasySong>> easySongs = new HashMap<>();
public Map<String, List<EasySong>> getEasySongs() {
return easySongs;
}
public void setEasySongs(Map<String, List<EasySong>> easySongs) {
this.easySongs = easySongs;
}
}
class EasySong {
private String title;
private int level;
private float percent;
private int id;
private String type;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
public float getPercent() {
return percent;
}
public void setPercent(float percent) {
this.percent = percent;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
} }

View File

@@ -16,8 +16,6 @@ import android.webkit.WebViewClient;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
@@ -32,17 +30,14 @@ import org.astral.findmaimaiultra.been.Release;
import org.astral.findmaimaiultra.databinding.FragmentSlideshowBinding; import org.astral.findmaimaiultra.databinding.FragmentSlideshowBinding;
import org.astral.findmaimaiultra.service.GitHubApiService; import org.astral.findmaimaiultra.service.GitHubApiService;
import org.astral.findmaimaiultra.ui.ImagePickerListener; import org.astral.findmaimaiultra.ui.ImagePickerListener;
import org.astral.findmaimaiultra.ui.LinkQQBot; import org.astral.findmaimaiultra.ui.login.LinkQQBot;
import org.astral.findmaimaiultra.utill.GitHubApiClient; import org.astral.findmaimaiultra.utill.GitHubApiClient;
import org.w3c.dom.Text;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
import java.util.Objects; import java.util.Objects;
import static android.app.Activity.RESULT_OK;
public class SlideshowFragment extends Fragment { public class SlideshowFragment extends Fragment {
private SharedPreferences settingProperties; private SharedPreferences settingProperties;
private static final int REQUEST_CODE_PERMISSIONS = 1001; private static final int REQUEST_CODE_PERMISSIONS = 1001;

View File

@@ -41,7 +41,7 @@
android:id="@+id/key" android:id="@+id/key"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="key"/> android:hint="用户邮箱"/>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -53,11 +53,11 @@
android:id="@+id/safecode" android:id="@+id/safecode"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="安全码"/> android:hint="2fa应用内的6位数字代码(注册不用填)"/>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton android:layout_width="match_parent" <com.google.android.material.button.MaterialButton android:layout_width="match_parent"
android:text="绑定qq机器人" android:text="登录/注册 账号"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:id="@+id/bangding" android:id="@+id/bangding"

View File

@@ -158,7 +158,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="踩" android:paddingLeft="16sp" android:text="踩" android:paddingLeft="16sp"
android:layout_marginLeft="2mm" android:layout_marginLeft="1mm"
android:layout_centerInParent="true"/> android:layout_centerInParent="true"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/list" android:id="@+id/list"
@@ -166,7 +166,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="16sp" android:paddingLeft="16sp"
android:text="评论" android:text="评论"
android:layout_marginLeft="2mm" android:layout_marginLeft="1mm"
android:layout_centerInParent="true"/> android:layout_centerInParent="true"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/move" android:id="@+id/move"
@@ -174,7 +174,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="16sp" android:paddingLeft="16sp"
android:text="移动" android:text="移动"
android:layout_marginLeft="2mm" android:layout_marginLeft="1mm"
android:layout_centerInParent="true"/> android:layout_centerInParent="true"/>
</LinearLayout> </LinearLayout>
<LinearLayout android:layout_width="match_parent" android:paddingLeft="16sp" android:layout_height="wrap_content" android:orientation="horizontal"> <LinearLayout android:layout_width="match_parent" android:paddingLeft="16sp" android:layout_height="wrap_content" android:orientation="horizontal">

View File

@@ -14,7 +14,8 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintVertical_bias="0.0"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab" android:id="@+id/fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -26,4 +27,95 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
tools:ignore="MissingConstraints" /> tools:ignore="MissingConstraints" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:minHeight="100dp"
android:background="@android:color/white"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<LinearLayout
android:id="@+id/bac"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="16dp"
tools:ignore="MissingConstraints">
<!-- 用户头像 -->
<ImageView
android:id="@+id/useravatar"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginEnd="16dp" />
<!-- 用户名容器 -->
<FrameLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:background="@color/white"
>
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="未绑定"
android:textSize="20sp"
android:textColor="@color/textcolorPrimary"
android:gravity="center"
android:layout_gravity="center_vertical|start"/> <!-- 距离顶部4dp -->
</FrameLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/login"
android:text="登录"
android:textColor="@color/white"
android:backgroundTint="@color/colorPrimary"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_gravity="center_horizontal"/>
<TextView android:layout_width="match_parent" android:layout_height="wrap_content"
android:textColor="@color/textcolorPrimary"
android:text="⭐推分建议⭐"
android:padding="8dp"
android:id="@+id/su"
android:textSize="18sp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/suggestion"
android:layout_width="match_parent"
android:layout_height="300dp"
app:layout_constraintTop_toTopOf="@id/su"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -163,7 +163,7 @@
android:id="@+id/openQQbot" android:id="@+id/openQQbot"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="#QQ机器人相关#" android:text="#账号#"
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:backgroundTint="?attr/colorPrimary" android:backgroundTint="?attr/colorPrimary"

View File

@@ -0,0 +1,11 @@
<?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="match_parent"
android:orientation="vertical">
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
android:layout_margin="2dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 图片部分 -->
<ImageView
android:id="@+id/backgroundLayout"
android:layout_width="140dp"
android:layout_height="140dp"
android:scaleType="centerCrop"
android:layout_alignParentStart="true"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp"
android:layout_toEndOf="@id/backgroundLayout"
android:layout_centerVertical="true">
<TextView
android:id="@+id/musicName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end" />
<TextView
android:id="@+id/level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp" />
<TextView
android:id="@+id/ach"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp" />
<ImageView
android:id="@+id/achievementImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerInside" />
</LinearLayout>
</RelativeLayout>
</androidx.cardview.widget.CardView>

View File

@@ -34,7 +34,7 @@
<string name="navigation_drawer_close">Close navigation drawer</string> <string name="navigation_drawer_close">Close navigation drawer</string>
<string name="menu_home">主页</string> <string name="menu_home">主页</string>
<string name="menu_gallery">地图</string> <string name="menu_gallery">地图</string>
<string name="menu_music">歌曲成绩</string> <string name="menu_music">账号</string>
<string name="menu_slideshow">设置</string> <string name="menu_slideshow">设置</string>
<string name="nav_header_title">FindMaimaiDX</string> <string name="nav_header_title">FindMaimaiDX</string>
<string name="nav_header_subtitle">Reisa</string> <string name="nav_header_subtitle">Reisa</string>