diff --git "a/D:\\wt" "b/D:\\wt"
deleted file mode 100644
index e69de29..0000000
diff --git a/MinimalistWeather/.gitignore b/MinimalistWeather/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/MinimalistWeather/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/MinimalistWeather/.idea/.gitignore b/MinimalistWeather/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/MinimalistWeather/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/MinimalistWeather/.idea/.name b/MinimalistWeather/.idea/.name
new file mode 100644
index 0000000..b3405b3
--- /dev/null
+++ b/MinimalistWeather/.idea/.name
@@ -0,0 +1 @@
+My Application
\ No newline at end of file
diff --git a/MinimalistWeather/.idea/AndroidProjectSystem.xml b/MinimalistWeather/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/MinimalistWeather/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/.idea/appInsightsSettings.xml b/MinimalistWeather/.idea/appInsightsSettings.xml
new file mode 100644
index 0000000..6bbe2ae
--- /dev/null
+++ b/MinimalistWeather/.idea/appInsightsSettings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/.idea/compiler.xml b/MinimalistWeather/.idea/compiler.xml
new file mode 100644
index 0000000..b86273d
--- /dev/null
+++ b/MinimalistWeather/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/.idea/deploymentTargetSelector.xml b/MinimalistWeather/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..911166f
--- /dev/null
+++ b/MinimalistWeather/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/.idea/gradle.xml b/MinimalistWeather/.idea/gradle.xml
new file mode 100644
index 0000000..639c779
--- /dev/null
+++ b/MinimalistWeather/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/.idea/inspectionProfiles/Project_Default.xml b/MinimalistWeather/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..7061a0d
--- /dev/null
+++ b/MinimalistWeather/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/.idea/kotlinc.xml b/MinimalistWeather/.idea/kotlinc.xml
new file mode 100644
index 0000000..c224ad5
--- /dev/null
+++ b/MinimalistWeather/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/.idea/migrations.xml b/MinimalistWeather/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/MinimalistWeather/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/.idea/misc.xml b/MinimalistWeather/.idea/misc.xml
new file mode 100644
index 0000000..b2c751a
--- /dev/null
+++ b/MinimalistWeather/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/.idea/runConfigurations.xml b/MinimalistWeather/.idea/runConfigurations.xml
new file mode 100644
index 0000000..16660f1
--- /dev/null
+++ b/MinimalistWeather/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/.idea/vcs.xml b/MinimalistWeather/.idea/vcs.xml
new file mode 100644
index 0000000..d843f34
--- /dev/null
+++ b/MinimalistWeather/.idea/vcs.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/.kotlin/errors/errors-1745675571085.log b/MinimalistWeather/.kotlin/errors/errors-1745675571085.log
new file mode 100644
index 0000000..1219b50
--- /dev/null
+++ b/MinimalistWeather/.kotlin/errors/errors-1745675571085.log
@@ -0,0 +1,4 @@
+kotlin version: 2.0.21
+error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
+ 1. Kotlin compile daemon is ready
+
diff --git a/MinimalistWeather/app/.gitignore b/MinimalistWeather/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/MinimalistWeather/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/MinimalistWeather/app/build.gradle.kts b/MinimalistWeather/app/build.gradle.kts
new file mode 100644
index 0000000..c06deb5
--- /dev/null
+++ b/MinimalistWeather/app/build.gradle.kts
@@ -0,0 +1,135 @@
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.kotlin.compose)
+}
+
+android {
+ namespace = "com.example.myapplication"
+ compileSdk = 35
+
+ defaultConfig {
+ applicationId = "com.example.myapplication"
+ minSdk = 24
+ targetSdk = 35
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+ buildFeatures {
+ compose = true
+ }
+
+ // 配置lint选项
+ lint {
+ // 使用警告而不是错误
+ abortOnError = false
+ // 创建基准
+ baseline = file("lint-baseline.xml")
+ }
+
+ // 添加解决重复类的配置
+ configurations.all {
+ resolutionStrategy {
+ // 防止依赖冲突
+ failOnVersionConflict()
+ // 优先使用本地依赖
+ preferProjectModules()
+ }
+ // 排除META-INF中可能重复的文件
+ exclude(group = "META-INF", module = "androidx.appcompat_appcompat.version")
+ }
+
+ // 设置Packaging选项以处理重复文件
+ packaging {
+ resources {
+ // 排除所有META-INF下的版本信息,防止冲突
+ excludes.add("META-INF/*.version")
+ excludes.add("META-INF/androidx.*")
+ excludes.add("META-INF/proguard/*")
+ excludes.add("META-INF/DEPENDENCIES")
+ excludes.add("META-INF/LICENSE")
+ excludes.add("META-INF/LICENSE.txt")
+ excludes.add("META-INF/license.txt")
+ excludes.add("META-INF/NOTICE")
+ excludes.add("META-INF/NOTICE.txt")
+ excludes.add("META-INF/notice.txt")
+ excludes.add("META-INF/*.kotlin_module")
+ // 更精确地排除导致冲突的文件
+ excludes.add("META-INF/androidx.appcompat_appcompat.version")
+ }
+ }
+}
+
+dependencies {
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.androidx.activity.compose)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.ui)
+ implementation(libs.androidx.ui.graphics)
+ implementation(libs.androidx.ui.tooling.preview)
+ implementation(libs.androidx.material3)
+
+ // AppCompat依赖 - 只保留一个
+ implementation("androidx.appcompat:appcompat:1.6.1") {
+ // 排除掉可能冲突的模块
+ exclude(group = "androidx.appcompat", module = "appcompat-resources")
+ }
+ // 不再使用本地AppCompat依赖,因为可能缺少必要的依赖项
+ // implementation(files("libs/appcompat-1.6.1.aar"))
+
+ // 添加AppCompat的依赖项
+ implementation("androidx.annotation:annotation:1.7.1")
+ implementation("androidx.core:core:1.12.0")
+ implementation("androidx.fragment:fragment:1.6.2")
+
+ // ConstraintLayout依赖
+ implementation("androidx.constraintlayout:constraintlayout:2.1.4")
+
+ // 添加CardView依赖
+ implementation("androidx.cardview:cardview:1.0.0")
+
+ // 添加RecyclerView依赖
+ implementation("androidx.recyclerview:recyclerview:1.3.2")
+
+ // 添加OkHttp库用于网络请求 - 只保留一个
+ implementation("com.squareup.okhttp3:okhttp:4.12.0")
+ // 不再使用本地OkHttp依赖
+ // implementation(files("libs/okhttp-4.12.0.jar"))
+
+ // 添加Okio库(OkHttp的依赖)
+ implementation("com.squareup.okio:okio:2.10.0")
+ // 不再使用本地Okio依赖
+ // implementation(files("libs/okio-2.10.0.jar"))
+
+ // 添加Gson库用于JSON解析
+ implementation("com.google.code.gson:gson:2.10.1")
+
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+ androidTestImplementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(libs.androidx.ui.test.junit4)
+ debugImplementation(libs.androidx.ui.tooling)
+ debugImplementation(libs.androidx.ui.test.manifest)
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/libs/appcompat-1.6.1.aar b/MinimalistWeather/app/libs/appcompat-1.6.1.aar
new file mode 100644
index 0000000..33a2cdd
Binary files /dev/null and b/MinimalistWeather/app/libs/appcompat-1.6.1.aar differ
diff --git a/MinimalistWeather/app/libs/okhttp-4.12.0.jar b/MinimalistWeather/app/libs/okhttp-4.12.0.jar
new file mode 100644
index 0000000..faf3fa8
Binary files /dev/null and b/MinimalistWeather/app/libs/okhttp-4.12.0.jar differ
diff --git a/MinimalistWeather/app/libs/okio-2.10.0.jar b/MinimalistWeather/app/libs/okio-2.10.0.jar
new file mode 100644
index 0000000..eef1956
Binary files /dev/null and b/MinimalistWeather/app/libs/okio-2.10.0.jar differ
diff --git a/MinimalistWeather/app/lint-baseline.xml b/MinimalistWeather/app/lint-baseline.xml
new file mode 100644
index 0000000..794f86d
--- /dev/null
+++ b/MinimalistWeather/app/lint-baseline.xml
@@ -0,0 +1,1159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MinimalistWeather/app/proguard-rules.pro b/MinimalistWeather/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/MinimalistWeather/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt b/MinimalistWeather/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..e9283cf
--- /dev/null
+++ b/MinimalistWeather/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.example.myapplication
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.example.myapplication", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/AndroidManifest.xml b/MinimalistWeather/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..43d2b3d
--- /dev/null
+++ b/MinimalistWeather/app/src/main/AndroidManifest.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/assets/晴天背景设置说明.txt b/MinimalistWeather/app/src/main/assets/晴天背景设置说明.txt
new file mode 100644
index 0000000..603c7e8
--- /dev/null
+++ b/MinimalistWeather/app/src/main/assets/晴天背景设置说明.txt
@@ -0,0 +1,40 @@
+# 晴天背景设置说明
+
+我们已经将您提供的中国风景图片转换为Android应用的背景资源,现在当天气为晴天时,应用会自动显示这个中国风景背景,并且添加了半透明效果以确保数据清晰可见。
+
+## 已完成的修改
+
+1. 创建了`china_landscape.xml`文件,使用XML绘制的方式模拟了您提供的中国风景图片
+2. 创建了`sunny_china_landscape.xml`文件,引用中国风景图片并添加半透明白色覆盖层
+3. 修改了`Util.java`中的天气背景映射,将晴天背景设置为新创建的中国风景图片
+
+## 如何替换为实际图片
+
+如果您想使用实际的图片而不是XML绘制的模拟图,请按照以下步骤操作:
+
+1. 将您提供的图片文件重命名为`china_landscape.jpg`或`china_landscape.png`
+2. 将图片文件放入`app/src/main/res/drawable`目录
+3. 删除我们创建的`china_landscape.xml`文件(可选)
+
+项目会自动使用您提供的实际图片文件,而不是XML绘制的模拟图。
+
+## 半透明效果
+
+我们在`sunny_china_landscape.xml`文件中设置了44%透明的白色覆盖层,以确保天气数据能够清晰显示。如果您认为透明度不够或太多,可以修改以下代码中的透明度值:
+
+```xml
+
+```
+
+透明度值范围为00-FF,其中:
+- 00表示完全透明
+- FF表示完全不透明
+- 70表示约44%的透明度
+
+## 测试方法
+
+当天气为晴天时,应用会自动显示这个背景。您可以通过查询晴天的城市来测试,比如北京。
+
+## 注意事项
+
+Android资源命名规则要求文件名只能包含小写字母(a-z)、数字(0-9)和下划线(_),不能使用点(.)、空格或大写字母。
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/CityManagerActivity.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/CityManagerActivity.java
new file mode 100644
index 0000000..2b2b079
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/CityManagerActivity.java
@@ -0,0 +1,252 @@
+package com.example.myapplication;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.example.myapplication.model.City;
+import com.example.myapplication.utils.CityManager;
+import com.example.myapplication.utils.SettingsManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 城市管理活动
+ * 用于显示、添加、删除和选择城市
+ */
+public class CityManagerActivity extends AppCompatActivity {
+ private static final String TAG = "CityManagerActivity";
+
+ private EditText etNewCity;
+ private Button btnAddCity;
+ private RecyclerView rvCities;
+ private CityAdapter cityAdapter;
+ private List cityList = new ArrayList<>();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // 应用主题设置
+ SettingsManager.applyThemeSetting(this);
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_city_manager);
+
+ // 设置工具栏
+ Toolbar toolbar = findViewById(R.id.city_manager_toolbar);
+ setSupportActionBar(toolbar);
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setTitle(R.string.city_favorites);
+ }
+
+ // 初始化控件
+ initViews();
+
+ // 设置监听器
+ setupListeners();
+
+ // 加载城市列表
+ loadCities();
+ }
+
+ /**
+ * 初始化控件
+ */
+ private void initViews() {
+ etNewCity = findViewById(R.id.et_new_city);
+ btnAddCity = findViewById(R.id.btn_add_city);
+ rvCities = findViewById(R.id.rv_cities);
+
+ // 设置RecyclerView
+ rvCities.setLayoutManager(new LinearLayoutManager(this));
+ cityAdapter = new CityAdapter();
+ rvCities.setAdapter(cityAdapter);
+ }
+
+ /**
+ * 设置监听器
+ */
+ private void setupListeners() {
+ btnAddCity.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String cityName = etNewCity.getText().toString().trim();
+ if (cityName.isEmpty()) {
+ Toast.makeText(CityManagerActivity.this, "请输入城市名称", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // 添加城市到收藏列表
+ boolean added = CityManager.addCity(CityManagerActivity.this, cityName, false);
+ if (added) {
+ Toast.makeText(CityManagerActivity.this,
+ getString(R.string.city_added_to_favorites, cityName),
+ Toast.LENGTH_SHORT).show();
+ etNewCity.setText("");
+
+ // 刷新城市列表
+ loadCities();
+ } else {
+ Toast.makeText(CityManagerActivity.this,
+ getString(R.string.city_already_in_favorites, cityName),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ }
+
+ /**
+ * 加载城市列表
+ */
+ private void loadCities() {
+ cityList = CityManager.getAllCities(this);
+ cityAdapter.notifyDataSetChanged();
+ }
+
+ /**
+ * 返回按钮处理
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ // 返回主活动并刷新数据(使用平滑过渡)
+ Intent intent = new Intent(this, WeatherActivity.class);
+ intent.putExtra("refresh_settings", true);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ startActivity(intent);
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * 返回键处理
+ */
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ // 返回主活动并刷新数据(使用平滑过渡)
+ Intent intent = new Intent(this, WeatherActivity.class);
+ intent.putExtra("refresh_settings", true);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ startActivity(intent);
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
+ finish();
+ }
+
+ /**
+ * 城市适配器
+ */
+ private class CityAdapter extends RecyclerView.Adapter {
+
+ @NonNull
+ @Override
+ public CityViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_city, parent, false);
+ return new CityViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull CityViewHolder holder, int position) {
+ City city = cityList.get(position);
+ holder.tvCityName.setText(city.getName());
+
+ // 检查是否为暗黑模式
+ boolean isDarkMode = SettingsManager.isDarkModeEnabled(CityManagerActivity.this);
+
+ // 设置选中状态
+ if (city.isSelected()) {
+ holder.tvCityName.setTextColor(getResources().getColor(R.color.colorPrimary));
+ holder.tvCurrentCity.setVisibility(View.VISIBLE);
+ } else {
+ // 根据模式选择文字颜色
+ if (isDarkMode) {
+ holder.tvCityName.setTextColor(getResources().getColor(android.R.color.white));
+ } else {
+ holder.tvCityName.setTextColor(getResources().getColor(android.R.color.black));
+ }
+ holder.tvCurrentCity.setVisibility(View.GONE);
+ }
+
+ // 设置点击事件
+ holder.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // 设置当前城市
+ CityManager.setCurrentCity(CityManagerActivity.this, city.getName());
+
+ // 刷新列表
+ loadCities();
+
+ Toast.makeText(CityManagerActivity.this,
+ getString(R.string.set_as_current_city, city.getName()),
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ // 设置删除按钮点击事件
+ holder.btnDelete.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // 检查是否是最后一个城市
+ if (cityList.size() <= 1) {
+ Toast.makeText(CityManagerActivity.this,
+ R.string.must_keep_one_city,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // 删除城市
+ CityManager.deleteCity(CityManagerActivity.this, city.getName());
+
+ // 刷新列表
+ loadCities();
+
+ Toast.makeText(CityManagerActivity.this,
+ getString(R.string.city_deleted, city.getName()),
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return cityList.size();
+ }
+
+ /**
+ * 城市ViewHolder
+ */
+ class CityViewHolder extends RecyclerView.ViewHolder {
+ TextView tvCityName;
+ TextView tvCurrentCity;
+ ImageButton btnDelete;
+
+ CityViewHolder(@NonNull View itemView) {
+ super(itemView);
+ tvCityName = itemView.findViewById(R.id.tv_city_name);
+ tvCurrentCity = itemView.findViewById(R.id.tv_current_city);
+ btnDelete = itemView.findViewById(R.id.btn_delete_city);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/MainActivity.kt b/MinimalistWeather/app/src/main/java/com/example/myapplication/MainActivity.kt
new file mode 100644
index 0000000..7f2a7e0
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/MainActivity.kt
@@ -0,0 +1,49 @@
+package com.example.myapplication
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.example.myapplication.ui.theme.MyApplicationTheme
+
+class MainActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ MyApplicationTheme {
+ // 使用Surface作为容器,而不是Scaffold
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.background
+ ) {
+ Greeting("Android", Modifier.padding(16.dp))
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun Greeting(name: String, modifier: Modifier = Modifier) {
+ Text(
+ text = "你好, $name!",
+ modifier = modifier
+ )
+}
+
+@Preview(showBackground = true)
+@Composable
+fun GreetingPreview() {
+ MyApplicationTheme {
+ Greeting("Android")
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/OkhttpActivity.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/OkhttpActivity.java
new file mode 100644
index 0000000..106e10a
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/OkhttpActivity.java
@@ -0,0 +1,302 @@
+package com.example.myapplication;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.example.myapplication.model.Forecast;
+import com.example.myapplication.utils.Util;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+public class OkhttpActivity extends AppCompatActivity implements View.OnClickListener {
+
+ private EditText etCity;
+ private Button btnRealTime;
+ private Button btnForecast;
+ private TextView tvResult;
+
+ // API KEY,用于访问高德地图天气API
+ private static final String API_KEY = "919f247fcef78d48763f563b1a194f48";
+
+ // 实时天气和天气预报URL模板
+ private static final String REAL_TIME_URL_TEMPLATE =
+ "https://restapi.amap.com/v3/weather/weatherInfo?city=CITY&key=" + API_KEY + "&extensions=base";
+ private static final String FORECAST_URL_TEMPLATE =
+ "https://restapi.amap.com/v3/weather/weatherInfo?city=CITY&key=" + API_KEY + "&extensions=all";
+
+ // OkHttpClient实例
+ private OkHttpClient client;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_okhttp);
+
+ // 初始化OkHttpClient
+ client = new OkHttpClient();
+
+ // 初始化UI控件
+ initViews();
+
+ // 设置点击事件监听器
+ setupListeners();
+ }
+
+ /**
+ * 初始化UI控件
+ */
+ private void initViews() {
+ etCity = findViewById(R.id.et_city);
+ btnRealTime = findViewById(R.id.btn_real_time);
+ btnForecast = findViewById(R.id.btn_forecast);
+ tvResult = findViewById(R.id.tv_result);
+ }
+
+ /**
+ * 设置按钮点击监听器
+ */
+ private void setupListeners() {
+ btnRealTime.setOnClickListener(this);
+ btnForecast.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ String cityName = etCity.getText().toString().trim();
+ if (cityName.isEmpty()) {
+ Toast.makeText(this, "请输入城市名称", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // 获取城市编码
+ String cityCode = Util.getCityCode(cityName);
+
+ if (v.getId() == R.id.btn_real_time) {
+ // 获取实时天气
+ getRealTimeWeather(cityCode);
+ } else if (v.getId() == R.id.btn_forecast) {
+ // 获取天气预报
+ getForcastWeather(cityCode);
+ }
+ }
+
+ /**
+ * 获取实时天气信息
+ * @param city 城市名称或编码
+ */
+ private void getRealTimeWeather(String city) {
+ // 构建URL,替换CITY占位符
+ String url = REAL_TIME_URL_TEMPLATE.replace("CITY", city);
+
+ // 创建请求
+ Request request = new Request.Builder().url(url).build();
+
+ // 创建Call对象
+ Call call = client.newCall(request);
+
+ // 异步执行请求
+ call.enqueue(new Callback() {
+ @Override
+ public void onFailure(Call call, IOException e) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(OkhttpActivity.this, "网络请求失败", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) throws IOException {
+ // 解析响应数据
+ String json = response.body().string();
+
+ try {
+ JSONObject object = new JSONObject(json);
+ String status = object.getString("status");
+
+ if ("1".equals(status)) {
+ JSONArray lives = object.getJSONArray("lives");
+ if (lives.length() > 0) {
+ JSONObject liveData = lives.getJSONObject(0);
+
+ final String city = liveData.getString("city");
+ final String weather = liveData.getString("weather");
+ final int temperature = liveData.getInt("temperature");
+ final String windDirection = liveData.getString("winddirection");
+ final String windPower = liveData.getString("windpower");
+ final int humidity = liveData.getInt("humidity");
+ final String reportTime = liveData.getString("reporttime");
+
+ // 构建结果字符串
+ final StringBuilder resultBuilder = new StringBuilder();
+ resultBuilder.append("城市:").append(city).append("\n");
+ resultBuilder.append("天气:").append(weather).append("\n");
+ resultBuilder.append("温度:").append(temperature).append("°C\n");
+ resultBuilder.append("风向:").append(windDirection).append("\n");
+ resultBuilder.append("风力:").append(windPower).append("级\n");
+ resultBuilder.append("湿度:").append(humidity).append("%\n");
+ resultBuilder.append("发布时间:").append(reportTime);
+
+ // 在UI线程更新结果
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ tvResult.setText(resultBuilder.toString());
+ }
+ });
+ }
+ } else {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(OkhttpActivity.this, "获取天气信息失败", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ /**
+ * 获取天气预报信息
+ * @param city 城市名称或编码
+ */
+ private void getForcastWeather(String city) {
+ // 构建URL,替换CITY占位符
+ String url = FORECAST_URL_TEMPLATE.replace("CITY", city);
+
+ // 创建请求
+ final Request request = new Request.Builder().url(url).build();
+
+ // 创建Call对象
+ Call call = client.newCall(request);
+
+ // 异步执行请求
+ call.enqueue(new Callback() {
+ @Override
+ public void onFailure(Call call, IOException e) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(OkhttpActivity.this, "网络请求失败", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) throws IOException {
+ // 解析响应数据
+ String json = response.body().string();
+
+ try {
+ JSONObject object = new JSONObject(json);
+ String status = object.getString("status");
+
+ if ("1".equals(status)) {
+ JSONArray forecasts = object.getJSONArray("forecasts");
+ if (forecasts.length() > 0) {
+ JSONObject forecastData = forecasts.getJSONObject(0);
+ JSONArray casts = forecastData.getJSONArray("casts");
+
+ // 存储解析后的天气预报
+ final List forecastList = new ArrayList<>();
+
+ for (int i = 0; i < casts.length(); i++) {
+ JSONObject castData = casts.getJSONObject(i);
+
+ String date = castData.getString("date");
+ String dayWeather = castData.getString("dayweather");
+ String nightWeather = castData.getString("nightweather");
+ int dayTemp = castData.getInt("daytemp");
+ int nightTemp = castData.getInt("nighttemp");
+ String dayWind = castData.getString("daywind");
+ String nightWind = castData.getString("nightwind");
+ String dayPower = castData.getString("daypower");
+ String nightPower = castData.getString("nightpower");
+
+ // 计算星期,这里简化处理,使用索引表示
+ String week;
+ if (i == 0) {
+ week = "今天";
+ } else if (i == 1) {
+ week = "明天";
+ } else if (i == 2) {
+ week = "后天";
+ } else {
+ week = "第" + (i+1) + "天";
+ }
+
+ Forecast forecast = new Forecast(
+ date,
+ week,
+ dayWeather,
+ nightWeather,
+ dayTemp,
+ nightTemp,
+ dayWind,
+ nightWind,
+ dayPower,
+ nightPower
+ );
+
+ forecastList.add(forecast);
+ }
+
+ // 构建结果字符串
+ final StringBuilder resultBuilder = new StringBuilder();
+ resultBuilder.append("城市:").append(forecastData.getString("city")).append("\n\n");
+
+ for (Forecast fc : forecastList) {
+ resultBuilder.append("日期:").append(fc.getDate()).append(" ").append(fc.getWeek()).append("\n");
+ resultBuilder.append("白天天气:").append(fc.getDayWeather()).append("\n");
+ resultBuilder.append("夜间天气:").append(fc.getNightWeather()).append("\n");
+ resultBuilder.append("温度范围:").append(fc.getTemperatureRange()).append("\n");
+ resultBuilder.append("风向:白天 ").append(fc.getDayWind()).append(",夜间 ").append(fc.getNightWind()).append("\n");
+ resultBuilder.append("风力:白天 ").append(fc.getDayPower()).append("级,夜间 ").append(fc.getNightPower()).append("级\n\n");
+ }
+
+ // 在UI线程更新结果
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ tvResult.setText(resultBuilder.toString());
+ }
+ });
+ }
+ } else {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(OkhttpActivity.this, "获取天气预报失败", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/SettingsActivity.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/SettingsActivity.java
new file mode 100644
index 0000000..1419246
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/SettingsActivity.java
@@ -0,0 +1,245 @@
+package com.example.myapplication;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.MenuItem;
+import android.widget.CompoundButton;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.appcompat.widget.SwitchCompat;
+import androidx.appcompat.widget.Toolbar;
+
+import com.example.myapplication.utils.SettingsManager;
+
+/**
+ * 设置界面活动
+ * 提供温度单位切换和主题模式切换设置
+ */
+public class SettingsActivity extends AppCompatActivity {
+ private static final String TAG = "SettingsActivity";
+
+ // SharedPreferences 键值
+ public static final String PREFS_NAME = "WeatherSettings";
+ public static final String KEY_TEMP_UNIT = "temperature_unit";
+ public static final String KEY_THEME_MODE = "theme_mode";
+
+ // 温度单位常量
+ public static final String TEMP_UNIT_CELSIUS = "celsius";
+ public static final String TEMP_UNIT_FAHRENHEIT = "fahrenheit";
+
+ private RadioGroup tempUnitGroup;
+ private RadioButton celsiusRadio;
+ private RadioButton fahrenheitRadio;
+ private SwitchCompat themeSwitch; // 使用SwitchCompat而不是Switch
+
+ private SharedPreferences preferences;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // 使用默认主题,不做任何更改
+ super.onCreate(savedInstanceState);
+
+ try {
+ setContentView(R.layout.activity_settings);
+ Log.d(TAG, "设置页面开始初始化");
+
+ // 初始化SharedPreferences
+ preferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
+
+ // 设置工具栏
+ setupToolbar();
+
+ // 初始化控件
+ initViews();
+
+ // 加载已保存的设置
+ loadSavedSettings();
+
+ // 设置监听器
+ setupListeners();
+
+ Log.d(TAG, "设置页面初始化完成");
+ } catch (Exception e) {
+ Log.e(TAG, "初始化设置页面时出错: " + e.getMessage(), e);
+ Toast.makeText(this, "初始化设置页面时出错,请重试", Toast.LENGTH_SHORT).show();
+ finish(); // 如果初始化失败,直接关闭页面
+ }
+ }
+
+ /**
+ * 设置工具栏
+ */
+ private void setupToolbar() {
+ try {
+ Toolbar toolbar = findViewById(R.id.settings_toolbar);
+ setSupportActionBar(toolbar);
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setTitle("设置");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "设置工具栏失败: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 初始化控件
+ */
+ private void initViews() {
+ try {
+ tempUnitGroup = findViewById(R.id.temp_unit_group);
+ celsiusRadio = findViewById(R.id.celsius_radio);
+ fahrenheitRadio = findViewById(R.id.fahrenheit_radio);
+ themeSwitch = findViewById(R.id.theme_switch);
+ } catch (Exception e) {
+ Log.e(TAG, "初始化控件失败: " + e.getMessage());
+ throw e; // 重新抛出异常,因为这是致命错误
+ }
+ }
+
+ /**
+ * 加载已保存的设置
+ */
+ private void loadSavedSettings() {
+ try {
+ // 加载温度单位设置
+ String tempUnit = preferences.getString(KEY_TEMP_UNIT, TEMP_UNIT_CELSIUS);
+ if (TEMP_UNIT_CELSIUS.equals(tempUnit)) {
+ celsiusRadio.setChecked(true);
+ } else {
+ fahrenheitRadio.setChecked(true);
+ }
+
+ // 加载主题模式设置
+ boolean isDarkMode = preferences.getBoolean(KEY_THEME_MODE, false);
+ themeSwitch.setChecked(isDarkMode);
+ } catch (Exception e) {
+ Log.e(TAG, "加载设置失败: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 设置监听器
+ */
+ private void setupListeners() {
+ // 温度单位切换
+ setupTempUnitListener();
+
+ // 主题模式切换
+ setupThemeListener();
+ }
+
+ /**
+ * 设置温度单位监听器
+ */
+ private void setupTempUnitListener() {
+ try {
+ tempUnitGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(RadioGroup group, int checkedId) {
+ try {
+ SharedPreferences.Editor editor = preferences.edit();
+ if (checkedId == R.id.celsius_radio) {
+ editor.putString(KEY_TEMP_UNIT, TEMP_UNIT_CELSIUS);
+ Log.d(TAG, "温度单位已切换为摄氏度");
+ } else if (checkedId == R.id.fahrenheit_radio) {
+ editor.putString(KEY_TEMP_UNIT, TEMP_UNIT_FAHRENHEIT);
+ Log.d(TAG, "温度单位已切换为华氏度");
+ }
+ editor.apply();
+
+ Toast.makeText(SettingsActivity.this, "温度单位设置已保存", Toast.LENGTH_SHORT).show();
+ } catch (Exception e) {
+ Log.e(TAG, "保存温度单位设置失败: " + e.getMessage());
+ }
+ }
+ });
+ } catch (Exception e) {
+ Log.e(TAG, "设置温度单位监听器失败: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 设置主题模式监听器
+ */
+ private void setupThemeListener() {
+ try {
+ themeSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ try {
+ Log.d(TAG, "主题开关切换: " + (isChecked ? "暗黑模式" : "明亮模式"));
+
+ // 保存设置
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putBoolean(KEY_THEME_MODE, isChecked);
+ editor.apply();
+
+ Toast.makeText(SettingsActivity.this,
+ "主题已设置为" + (isChecked ? "暗黑模式" : "明亮模式"),
+ Toast.LENGTH_SHORT).show();
+
+ // 延迟应用主题,避免UI闪烁
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // 应用新主题
+ SettingsManager.applyThemeSetting(SettingsActivity.this);
+
+ // 稍微延迟后返回主页并传递设置更新标志
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Intent intent = new Intent(SettingsActivity.this, WeatherActivity.class);
+ intent.putExtra("refresh_settings", true);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ finish();
+ }
+ }, 300); // 延迟300毫秒,让用户感知到切换
+ } catch (Exception e) {
+ Log.e(TAG, "应用主题设置失败: " + e.getMessage());
+ }
+ }
+ }, 100); // 延迟100毫秒执行主题切换
+ } catch (Exception e) {
+ Log.e(TAG, "保存主题设置失败: " + e.getMessage());
+ Toast.makeText(SettingsActivity.this, "保存主题设置失败", Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ } catch (Exception e) {
+ Log.e(TAG, "设置主题切换监听器失败: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 返回按钮处理
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * 物理返回键处理
+ * 如果需要在返回前进行特殊处理(如保存数据),可以在调用super之前添加
+ */
+ @Override
+ public void onBackPressed() {
+ // 在此处可以添加返回前的特殊处理
+ super.onBackPressed();
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/WeatherActivity.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/WeatherActivity.java
new file mode 100644
index 0000000..35fe41f
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/WeatherActivity.java
@@ -0,0 +1,1640 @@
+package com.example.myapplication;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.widget.ImageButton;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.content.ContextCompat;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.ViewPropertyAnimatorCompat;
+import androidx.core.view.ViewPropertyAnimatorListener;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.example.myapplication.model.City;
+import com.example.myapplication.model.Forecast;
+import com.example.myapplication.model.WeatherAlert;
+import com.example.myapplication.model.WeatherInfo;
+import com.example.myapplication.service.AlertService;
+import com.example.myapplication.service.ForecastService;
+import com.example.myapplication.service.WeatherService;
+import com.example.myapplication.ui.AlertAdapter;
+import com.example.myapplication.utils.CityManager;
+import com.example.myapplication.utils.HttpUtils;
+import com.example.myapplication.utils.SettingsManager;
+import com.example.myapplication.utils.Util;
+import com.example.myapplication.utils.BackgroundManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import androidx.appcompat.app.AppCompatDelegate;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.provider.MediaStore;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.content.SharedPreferences;
+
+public class WeatherActivity extends AppCompatActivity {
+ private static final String TAG = "WeatherActivity";
+ private static final int REQUEST_CODE_PICK_IMAGE = 100;
+
+ private EditText etCityName;
+ private Button btnSearch;
+ private TextView tvCity;
+ private TextView tvWeather;
+ private TextView tvTemp;
+ private TextView tvWind;
+ private TextView tvHumidity;
+ private ImageView ivWeatherImg;
+ private TextView tvForecast1;
+ private TextView tvForecast2;
+ private TextView tvForecast3;
+ private TextView tvForecast4;
+ private LinearLayout weatherContainer;
+ private ImageView backgroundImageView;
+ private Toolbar toolbar;
+ private Spinner spinnerCities;
+ private ImageButton btnFavorite;
+
+ private String currentWeather = "";
+ private String currentCity = "";
+ private Handler autoUpdateHandler = new Handler();
+ private Runnable autoUpdateRunnable;
+ private List cityList = new ArrayList<>();
+ private ArrayAdapter cityAdapter;
+ private List cityNameList = new ArrayList<>();
+ private boolean isSpinnerInitialized = false;
+
+ private boolean themeChanging = false; // 标记主题是否正在变更
+ private long lastWeatherUpdateTime = 0; // 上次更新天气的时间
+
+ private RecyclerView rvAlerts; // 预警RecyclerView
+ private LinearLayout alertContainer; // 预警容器
+ private TextView tvNoAlerts; // 无预警提示
+ private AlertAdapter alertAdapter; // 预警适配器
+ private List alertList = new ArrayList<>(); // 预警列表
+
+ // 图片选择结果处理
+ private ActivityResultLauncher imagePickerLauncher;
+ private ActivityResultLauncher requestPermissionLauncher;
+
+ // 用于在配置变更时保存当前城市和天气信息
+ private static final String STATE_CURRENT_CITY = "current_city";
+ private static final String STATE_CURRENT_WEATHER = "current_weather";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ try {
+ // 强制使用亮色主题已被移除,现在使用系统设置
+ // AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
+
+ // 应用主题设置(无闪烁)
+ themeChanging = true; // 标记正在变更主题
+ SettingsManager.applyThemeSetting(this);
+ themeChanging = false;
+
+ // 设置过渡动画
+ getWindow().setAllowEnterTransitionOverlap(true);
+
+ // 继续正常初始化
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_weather);
+ Log.d(TAG, "onCreate: 开始初始化");
+
+ // 初始化控件
+ initViews();
+
+ // 如果有保存的状态,恢复状态
+ if (savedInstanceState != null) {
+ currentCity = savedInstanceState.getString(STATE_CURRENT_CITY, "");
+ currentWeather = savedInstanceState.getString(STATE_CURRENT_WEATHER, "");
+ Log.d(TAG, "从savedInstanceState恢复状态: 城市=" + currentCity + ", 天气=" + currentWeather);
+ }
+
+ // 设置工具栏
+ setSupportActionBar(toolbar);
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setTitle(""); // 清空标题,因为我们使用了自定义的Spinner
+ }
+
+ // 设置半透明状态栏和导航栏,确保全屏沉浸式体验
+ getWindow().setStatusBarColor(Color.TRANSPARENT);
+
+ // 根据是否为暗黑模式应用相应的半透明颜色
+ if (SettingsManager.isDarkModeEnabled(this)) {
+ applyDarkModeToToolbar();
+ } else {
+ // 普通模式下使用半透明黑色
+ if (toolbar != null) {
+ toolbar.setBackgroundColor(Color.argb(96, 0, 0, 0)); // 降低不透明度到约38%
+ }
+ View searchContainer = findViewById(R.id.search_container);
+ if (searchContainer != null) {
+ searchContainer.setBackgroundColor(Color.argb(96, 0, 0, 0)); // 降低不透明度到约38%
+ }
+ getWindow().setNavigationBarColor(Color.argb(96, 0, 0, 0));
+ }
+
+ // 设置点击事件
+ setupListeners();
+
+ // 初始化预警适配器
+ setupAlertAdapter();
+
+ // 初始化图片选择器
+ setupImagePicker();
+ setupPermissionLauncher();
+
+ // 测试API是否可用
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ boolean apiWorking = com.example.myapplication.utils.APITester.testWeatherAPI();
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (!apiWorking) {
+ Toast.makeText(WeatherActivity.this, "天气API不可用,请检查网络连接和API密钥", Toast.LENGTH_LONG).show();
+ } else {
+ Log.d(TAG, "API测试成功,可以正常获取天气数据");
+ }
+ }
+ });
+ }
+ }).start();
+
+ // 加载默认城市
+ loadDefaultCity();
+
+ // 检查是否从设置页面返回
+ if (getIntent().getBooleanExtra("refresh_settings", false)) {
+ Log.d(TAG, "从设置页面返回,刷新数据");
+ // 刷新天气数据
+ refreshCitySpinner();
+ if (!currentCity.isEmpty()) {
+ queryCurrentWeather(currentCity);
+ queryWeatherForecast(currentCity);
+ queryWeatherAlerts(currentCity);
+ }
+ }
+
+ // 设置自动更新
+ setupAutoUpdate();
+
+ // 设置输入法动作监听
+ etCityName.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+ btnSearch.performClick(); // 模拟点击搜索按钮
+ return true;
+ }
+ return false;
+ }
+ });
+
+ Log.d(TAG, "onCreate: 初始化完成");
+ } catch (Exception e) {
+ Log.e(TAG, "onCreate: 初始化失败", e);
+ e.printStackTrace();
+ Toast.makeText(this, "应用初始化失败: " + e.getMessage(), Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * 在配置变更前保存状态
+ */
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ // 保存当前城市和天气信息
+ outState.putString(STATE_CURRENT_CITY, currentCity);
+ outState.putString(STATE_CURRENT_WEATHER, currentWeather);
+ Log.d(TAG, "保存状态: 城市=" + currentCity + ", 天气=" + currentWeather);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ try {
+ // 检查是否需要重新应用主题
+ boolean themeChanged = false;
+ boolean isDarkMode = SettingsManager.isDarkModeEnabled(this);
+ boolean currentDarkMode = (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES);
+
+ if (isDarkMode != currentDarkMode) {
+ themeChanging = true;
+ themeChanged = true;
+ SettingsManager.applyThemeSetting(this);
+ themeChanging = false;
+ }
+
+ // 始终保持状态栏透明
+ getWindow().setStatusBarColor(Color.TRANSPARENT);
+
+ // 记住当前城市(防止在刷新过程中丢失)
+ String rememberedCity = currentCity;
+ String rememberedWeather = currentWeather;
+
+ // 刷新城市下拉列表(不触发天气获取)
+ refreshCitySpinner();
+
+ // 如果是主题变更,恢复用户当前查询的城市
+ if (themeChanged) {
+ if (!rememberedCity.isEmpty()) {
+ // 恢复当前城市和天气状态
+ currentCity = rememberedCity;
+ currentWeather = rememberedWeather;
+ } else {
+ // 如果没有记住的城市,使用北京作为默认城市
+ currentCity = "北京";
+ etCityName.setText(currentCity);
+ }
+
+ // 恢复UI状态
+ restoreUIAfterThemeChange();
+ }
+
+ // 根据是否为暗黑模式应用相应的半透明颜色
+ if (isDarkMode) {
+ applyDarkModeToToolbar();
+ } else {
+ // 普通模式下使用半透明黑色
+ if (toolbar != null) {
+ toolbar.setBackgroundColor(Color.argb(96, 0, 0, 0)); // 降低不透明度到约38%
+ }
+ View searchContainer = findViewById(R.id.search_container);
+ if (searchContainer != null) {
+ searchContainer.setBackgroundColor(Color.argb(96, 0, 0, 0)); // 降低不透明度到约38%
+ }
+ getWindow().setNavigationBarColor(Color.argb(96, 0, 0, 0));
+ }
+
+ // 检查是否需要重新查询天气(避免重复查询)
+ long currentTime = System.currentTimeMillis();
+ boolean shouldRefreshWeather = false;
+
+ // 判断是否需要刷新天气:
+ // 1. 如果从未获取过天气数据(lastWeatherUpdateTime==0)且不是主题变更
+ // 2. 如果距离上次更新已经过去了至少30秒且不是主题变更
+ // 3. 只有在非主题变更或明确请求刷新的情况下才查询
+ if (!themeChanged && (lastWeatherUpdateTime == 0 ||
+ (currentTime - lastWeatherUpdateTime >= 30 * 1000))) {
+ shouldRefreshWeather = true;
+ }
+
+ // 只有需要时才刷新天气
+ if (shouldRefreshWeather && !currentCity.isEmpty()) {
+ Log.d(TAG, "刷新天气信息");
+ queryCurrentWeather(currentCity);
+ queryWeatherForecast(currentCity);
+ queryWeatherAlerts(currentCity);
+ lastWeatherUpdateTime = currentTime;
+ } else {
+ Log.d(TAG, "跳过天气刷新" + (themeChanged ? "(主题变更)" : "")
+ + ",距上次更新时间: " + ((currentTime - lastWeatherUpdateTime) / 1000) + "秒");
+ }
+
+ } catch (Exception e) {
+ Log.e(TAG, "onResume异常: " + e.getMessage(), e);
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ try {
+ // 停止自动更新
+ stopAutoUpdate();
+
+ // 如果正在进行主题切换,不保存状态
+ if (themeChanging) {
+ Log.d(TAG, "onPause: 主题切换中,跳过状态保存");
+ return;
+ }
+
+ // 可以在这里保存一些状态,确保主题切换后能恢复
+ Log.d(TAG, "onPause: 应用进入后台或即将销毁");
+ } catch (Exception e) {
+ Log.e(TAG, "onPause: 出错", e);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ int id = item.getItemId();
+
+ if (id == R.id.action_settings) {
+ try {
+ Log.d(TAG, "尝试打开设置页面");
+
+ // 使用最简单的方式打开设置页面,不添加任何特殊标识
+ Intent intent = new Intent();
+ intent.setClass(this, SettingsActivity.class);
+ startActivity(intent);
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "打开设置页面时出错: " + e.getMessage(), e);
+ Toast.makeText(this, "无法打开设置页面,请稍后重试", Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ } else if (id == R.id.action_refresh) {
+ // 刷新当前天气
+ if (!currentCity.isEmpty()) {
+ queryCurrentWeather(currentCity);
+ queryWeatherForecast(currentCity);
+ }
+ return true;
+ } else if (id == R.id.action_city_manager) {
+ try {
+ // 打开城市管理页面(添加平滑过渡)
+ Intent intent = new Intent(this, CityManagerActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ startActivity(intent);
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "打开城市管理页面时出错: " + e.getMessage(), e);
+ Toast.makeText(this, "无法打开城市管理页面,请稍后重试", Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ } else if (id == R.id.action_share) {
+ // 分享天气信息
+ shareWeatherInfo();
+ return true;
+ } else if (id == R.id.action_gallery) {
+ // 检查并请求权限,然后打开图片选择器
+ checkAndRequestPermission();
+ return true;
+ } else if (id == R.id.action_reset_background) {
+ // 恢复默认背景
+ if (currentWeather.isEmpty()) {
+ Toast.makeText(this, "请先查询天气", Toast.LENGTH_SHORT).show();
+ return true;
+ }
+
+ // 是否确认恢复默认背景
+ if (BackgroundManager.hasCustomBackground(this, currentWeather)) {
+ new android.app.AlertDialog.Builder(this)
+ .setTitle("确认恢复")
+ .setMessage("确定要恢复" + currentWeather + "天气的默认背景吗?")
+ .setPositiveButton("确定", (dialog, which) -> {
+ BackgroundManager.resetBackground(this, currentWeather);
+ updateBackground(currentWeather);
+ Toast.makeText(this, "已恢复默认背景", Toast.LENGTH_SHORT).show();
+ })
+ .setNegativeButton("取消", null)
+ .show();
+ } else {
+ Toast.makeText(this, "当前天气类型未设置自定义背景", Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**
+ * 初始化控件
+ */
+ private void initViews() {
+ try {
+ Log.d(TAG, "initViews: 开始初始化控件");
+ toolbar = findViewById(R.id.toolbar);
+ etCityName = findViewById(R.id.cityname);
+ btnSearch = findViewById(R.id.search);
+ tvCity = findViewById(R.id.city);
+ tvWeather = findViewById(R.id.weather);
+ tvTemp = findViewById(R.id.temp);
+ tvWind = findViewById(R.id.wind);
+ tvHumidity = findViewById(R.id.humidity);
+ ivWeatherImg = findViewById(R.id.img);
+ tvForecast1 = findViewById(R.id.forcast1);
+ tvForecast2 = findViewById(R.id.forcast2);
+ tvForecast3 = findViewById(R.id.forcast3);
+ tvForecast4 = findViewById(R.id.forcast4);
+ weatherContainer = findViewById(R.id.weather_container);
+ backgroundImageView = findViewById(R.id.background_image);
+ spinnerCities = findViewById(R.id.spinner_cities);
+ btnFavorite = findViewById(R.id.btn_favorite);
+
+ // 初始化天气预警UI组件
+ rvAlerts = findViewById(R.id.rvAlerts);
+ alertContainer = findViewById(R.id.alertContainer);
+ tvNoAlerts = findViewById(R.id.tvNoAlerts);
+
+ Log.d(TAG, "initViews: 控件初始化完成");
+ } catch (Exception e) {
+ Log.e(TAG, "initViews: 初始化UI失败", e);
+ e.printStackTrace();
+ Toast.makeText(this, "初始化UI失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ /**
+ * 设置监听器
+ */
+ private void setupListeners() {
+ btnSearch.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ try {
+ String cityName = etCityName.getText().toString().trim();
+ if (cityName.isEmpty()) {
+ Toast.makeText(WeatherActivity.this, "请输入城市名称", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // 检查网络连接
+ if (!HttpUtils.isNetworkAvailable(WeatherActivity.this)) {
+ Toast.makeText(WeatherActivity.this, "网络不可用,请检查网络连接", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // 设置当前城市用于显示天气,但不自动添加到收藏列表
+ currentCity = cityName;
+
+ // 获取当前天气
+ queryCurrentWeather(cityName);
+
+ // 获取天气预报
+ queryWeatherForecast(cityName);
+
+ // 更新收藏按钮状态
+ updateFavoriteButtonState();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ Toast.makeText(WeatherActivity.this, "查询天气时出错: " + e.getMessage(), Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+
+ // 设置收藏按钮点击事件
+ btnFavorite.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ toggleFavoriteCity();
+ }
+ });
+ }
+
+ /**
+ * 切换城市收藏状态
+ */
+ private void toggleFavoriteCity() {
+ if (currentCity.isEmpty()) {
+ return;
+ }
+
+ // 检查城市是否已在收藏列表中
+ boolean isFavorited = false;
+ List cities = CityManager.getAllCities(this);
+ for (City city : cities) {
+ if (city.getName().equals(currentCity)) {
+ isFavorited = true;
+ break;
+ }
+ }
+
+ if (isFavorited) {
+ // 如果城市已在收藏列表中,询问是否要取消收藏
+ android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(this);
+ builder.setTitle(R.string.remove_from_favorites_title);
+ builder.setMessage(getString(R.string.remove_from_favorites_message, currentCity));
+ builder.setPositiveButton(R.string.yes, new android.content.DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(android.content.DialogInterface dialog, int which) {
+ // 删除城市
+ if (CityManager.deleteCity(WeatherActivity.this, currentCity)) {
+ Toast.makeText(WeatherActivity.this,
+ getString(R.string.removed_from_favorites, currentCity),
+ Toast.LENGTH_SHORT).show();
+ // 更新UI
+ updateFavoriteButtonState();
+ refreshCitySpinner();
+ }
+ }
+ });
+ builder.setNegativeButton(R.string.no, null);
+ builder.show();
+ } else {
+ // 如果城市不在收藏列表中,添加到收藏
+ if (CityManager.addCity(this, currentCity, true)) {
+ Toast.makeText(this,
+ getString(R.string.city_added_to_favorites, currentCity),
+ Toast.LENGTH_SHORT).show();
+ // 更新UI
+ updateFavoriteButtonState();
+ refreshCitySpinner();
+ } else {
+ Toast.makeText(this,
+ getString(R.string.city_already_in_favorites, currentCity),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ /**
+ * 更新收藏按钮状态(星标)
+ */
+ private void updateFavoriteButtonState() {
+ if (currentCity.isEmpty() || btnFavorite == null) {
+ // 如果当前城市为空,隐藏收藏按钮
+ if (btnFavorite != null) {
+ btnFavorite.setVisibility(View.INVISIBLE);
+ }
+ return;
+ } else {
+ // 否则显示收藏按钮
+ btnFavorite.setVisibility(View.VISIBLE);
+ }
+
+ // 检查城市是否已在收藏列表中
+ boolean isFavorited = false;
+ List cities = CityManager.getAllCities(this);
+ for (City city : cities) {
+ if (city.getName().equals(currentCity)) {
+ isFavorited = true;
+ break;
+ }
+ }
+
+ // 设置按钮图标
+ if (isFavorited) {
+ btnFavorite.setImageResource(android.R.drawable.btn_star_big_on);
+ } else {
+ btnFavorite.setImageResource(android.R.drawable.btn_star_big_off);
+ }
+ }
+
+ /**
+ * 设置城市选择器
+ */
+ private void setupCitySpinner() {
+ try {
+ // 获取保存的城市列表
+ cityList = CityManager.getAllCities(this);
+ cityNameList.clear();
+
+ // 提取城市名称列表
+ String selectedCity = "";
+ for (City city : cityList) {
+ cityNameList.add(city.getName());
+ if (city.isSelected()) {
+ selectedCity = city.getName();
+ }
+ }
+
+ // 如果当前城市为空,使用选定的城市或第一个城市
+ if (currentCity.isEmpty()) {
+ if (!selectedCity.isEmpty()) {
+ currentCity = selectedCity;
+ } else if (!cityNameList.isEmpty()) {
+ currentCity = cityNameList.get(0);
+ CityManager.setCurrentCity(this, currentCity);
+ } else {
+ // 如果没有收藏城市,默认使用北京
+ currentCity = "北京";
+ }
+ if (!currentCity.isEmpty()) {
+ Log.d(TAG, "设置初始城市: " + currentCity);
+ }
+ }
+
+ // 设置适配器 - 使用自定义的显示风格
+ cityAdapter = new ArrayAdapter<>(this, R.layout.spinner_item, cityNameList);
+ cityAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
+ spinnerCities.setAdapter(cityAdapter);
+
+ // 如果没有收藏城市,隐藏下拉菜单
+ if (cityNameList.isEmpty()) {
+ spinnerCities.setVisibility(View.GONE);
+ } else {
+ spinnerCities.setVisibility(View.VISIBLE);
+
+ // 设置初始选中项
+ for (int i = 0; i < cityNameList.size(); i++) {
+ if (cityNameList.get(i).equals(currentCity)) {
+ spinnerCities.setSelection(i);
+ break;
+ }
+ }
+ }
+
+ // 设置选择监听器
+ spinnerCities.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ private String previouslySelectedCity = currentCity;
+
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ try {
+ // 检查城市列表是否为空
+ if (cityNameList.isEmpty()) {
+ return;
+ }
+
+ // 检查是否是初始化过程中的调用
+ if (!isSpinnerInitialized) {
+ isSpinnerInitialized = true;
+ Log.d(TAG, "Spinner初始化过程中的选择,不触发查询");
+ previouslySelectedCity = currentCity;
+ return;
+ }
+
+ // 检查是否是主题切换导致的选择
+ if (themeChanging) {
+ Log.d(TAG, "主题切换过程中的选择,不触发查询");
+ return;
+ }
+
+ String selectedCity = cityNameList.get(position);
+
+ // 检查是否真的改变了城市(避免重复查询)
+ if (!selectedCity.equals(previouslySelectedCity)) {
+ Log.d(TAG, "用户选择了新城市: " + selectedCity + " (之前: " + previouslySelectedCity + ")");
+ currentCity = selectedCity;
+ previouslySelectedCity = selectedCity;
+ updateFavoriteButtonState();
+
+ // 查询新城市的天气
+ queryCurrentWeather(selectedCity);
+ queryWeatherForecast(selectedCity);
+ queryWeatherAlerts(selectedCity);
+ } else {
+ Log.d(TAG, "选择了相同的城市: " + selectedCity + ",跳过查询");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "处理城市选择时出错: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ // 不做任何操作
+ }
+ });
+
+ // 设置搜索框文本并查询初始城市天气
+ if (!currentCity.isEmpty()) {
+ etCityName.setText(currentCity);
+ // 查询初始城市天气
+ queryCurrentWeather(currentCity);
+ queryWeatherForecast(currentCity);
+ }
+
+ } catch (Exception e) {
+ Log.e(TAG, "setupCitySpinner异常: " + e.getMessage(), e);
+ e.printStackTrace();
+
+ // 出现异常时,默认使用北京
+ if (currentCity.isEmpty()) {
+ currentCity = "北京";
+ etCityName.setText(currentCity);
+ queryCurrentWeather(currentCity);
+ queryWeatherForecast(currentCity);
+ }
+ }
+ }
+
+ /**
+ * 刷新城市下拉列表
+ */
+ private void refreshCitySpinner() {
+ try {
+ // 获取保存的城市列表
+ cityList = CityManager.getAllCities(this);
+ cityNameList.clear();
+
+ // 提取城市名称列表
+ String selectedCity = "";
+ for (City city : cityList) {
+ cityNameList.add(city.getName());
+ if (city.isSelected()) {
+ selectedCity = city.getName();
+ }
+ }
+
+ // 当currentCity为空时,才使用收藏列表中的选中城市
+ if (currentCity.isEmpty() && !selectedCity.isEmpty()) {
+ currentCity = selectedCity;
+ Log.d(TAG, "refreshCitySpinner: 使用收藏中的默认城市: " + currentCity);
+ }
+
+ // 更新适配器
+ if (cityAdapter != null) {
+ cityAdapter.notifyDataSetChanged();
+
+ // 如果收藏城市列表为空,隐藏下拉菜单
+ if (cityNameList.isEmpty()) {
+ spinnerCities.setVisibility(View.GONE);
+ } else {
+ spinnerCities.setVisibility(View.VISIBLE);
+
+ // 如果当前城市在收藏列表中,更新spinner选中项
+ boolean foundCurrentCity = false;
+ for (int i = 0; i < cityNameList.size(); i++) {
+ if (cityNameList.get(i).equals(currentCity)) {
+ spinnerCities.setSelection(i);
+ foundCurrentCity = true;
+ break;
+ }
+ }
+
+ // 如果在列表中找不到当前城市(可能是通过搜索得到的)
+ // 不做特殊处理,保持spinner的当前选择
+ if (!foundCurrentCity) {
+ Log.d(TAG, "当前城市 " + currentCity + " 不在收藏列表中");
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "refreshCitySpinner: 刷新城市选择器失败", e);
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 查询当前天气
+ * @param cityName 城市名称
+ */
+ private void queryCurrentWeather(String cityName) {
+ // 显示加载提示
+ Toast.makeText(this, "正在获取" + cityName + "的天气信息...", Toast.LENGTH_SHORT).show();
+
+ WeatherService.getCurrentWeather(cityName, new WeatherService.WeatherCallback() {
+ @Override
+ public void onSuccess(WeatherInfo weatherInfo) {
+ // 更新上次天气数据获取时间
+ lastWeatherUpdateTime = System.currentTimeMillis();
+ // 更新UI
+ updateWeatherUI(weatherInfo);
+
+ // 注意:这里不调用refreshCitySpinner(),避免自动切换Spinner的选中项
+ }
+
+ @Override
+ public void onError(String errorMsg) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(WeatherActivity.this, errorMsg, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * 查询天气预报
+ * @param cityName 城市名称
+ */
+ private void queryWeatherForecast(String cityName) {
+ ForecastService.getWeatherForecast(cityName, new ForecastService.ForecastCallback() {
+ @Override
+ public void onSuccess(List forecastList) {
+ // 更新UI
+ updateForecastUI(forecastList);
+ }
+
+ @Override
+ public void onError(String errorMsg) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(WeatherActivity.this, errorMsg, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * 更新当前天气UI
+ * @param weatherInfo 天气信息
+ */
+ private void updateWeatherUI(final WeatherInfo weatherInfo) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // 保存当前天气状况
+ currentWeather = weatherInfo.getWeather();
+
+ tvCity.setText("城市: " + weatherInfo.getCity());
+ tvWeather.setText("天气: " + currentWeather);
+
+ // 根据设置显示温度(摄氏度或华氏度)
+ String formattedTemp = SettingsManager.getFormattedTemperature(
+ WeatherActivity.this, weatherInfo.getTemperature());
+ tvTemp.setText("温度: " + formattedTemp);
+
+ tvWind.setText("风力风向: " + weatherInfo.getWindDirection() + " " + weatherInfo.getWindPower() + "级");
+ tvHumidity.setText("湿度: " + weatherInfo.getHumidity() + "%");
+
+ // 根据天气状况设置图片
+ setWeatherImage(currentWeather);
+
+ // 更新背景
+ updateBackground(currentWeather);
+
+ // 根据天气状况调整文字颜色
+ adjustTextColors(currentWeather);
+
+ // 更新收藏按钮状态
+ updateFavoriteButtonState();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ Toast.makeText(WeatherActivity.this, "更新UI时出错: " + e.getMessage(), Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ }
+
+ /**
+ * 更新天气预报UI
+ * @param forecastList 天气预报列表
+ */
+ private void updateForecastUI(final List forecastList) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (forecastList.size() >= 1) {
+ Forecast day1 = forecastList.get(0);
+ // 根据设置调整温度显示
+ String tempRange = day1.getNightTemperature() + "~" + day1.getDayTemperature();
+ String formattedRange = SettingsManager.getFormattedTemperatureRange(
+ WeatherActivity.this, day1.getNightTemperature(), day1.getDayTemperature());
+ tvForecast1.setText(day1.getWeek() + "\n" + day1.getDayWeather() + "\n" + formattedRange);
+ }
+
+ if (forecastList.size() >= 2) {
+ Forecast day2 = forecastList.get(1);
+ String formattedRange = SettingsManager.getFormattedTemperatureRange(
+ WeatherActivity.this, day2.getNightTemperature(), day2.getDayTemperature());
+ tvForecast2.setText(day2.getWeek() + "\n" + day2.getDayWeather() + "\n" + formattedRange);
+ }
+
+ if (forecastList.size() >= 3) {
+ Forecast day3 = forecastList.get(2);
+ String formattedRange = SettingsManager.getFormattedTemperatureRange(
+ WeatherActivity.this, day3.getNightTemperature(), day3.getDayTemperature());
+ tvForecast3.setText(day3.getWeek() + "\n" + day3.getDayWeather() + "\n" + formattedRange);
+ }
+
+ if (forecastList.size() >= 4) {
+ Forecast day4 = forecastList.get(3);
+ String formattedRange = SettingsManager.getFormattedTemperatureRange(
+ WeatherActivity.this, day4.getNightTemperature(), day4.getDayTemperature());
+ tvForecast4.setText(day4.getWeek() + "\n" + day4.getDayWeather() + "\n" + formattedRange);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ Toast.makeText(WeatherActivity.this, "更新天气预报UI时出错: " + e.getMessage(), Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ }
+
+ /**
+ * 根据天气状况设置图片
+ * @param weather 天气状况
+ */
+ private void setWeatherImage(String weather) {
+ try {
+ // 使用Util类获取对应的天气图片
+ int imageResourceId = Util.getWeatherImageByTime(weather);
+
+ // 安全设置图片资源
+ ivWeatherImg.setImageResource(imageResourceId);
+
+ // 设置图片尺寸
+ ivWeatherImg.getLayoutParams().width = 200; // 单位是像素
+ ivWeatherImg.getLayoutParams().height = 200;
+ ivWeatherImg.requestLayout();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ // 如果发生异常,使用默认图片
+ ivWeatherImg.setImageResource(R.drawable.sunny);
+ }
+ }
+
+ /**
+ * 根据天气状况更新背景
+ * @param weather 天气状况
+ */
+ private void updateBackground(String weather) {
+ try {
+ // 检查是否为暗黑模式
+ boolean isDarkMode = SettingsManager.isDarkModeEnabled(this);
+
+ // 清除背景
+ weatherContainer.setBackgroundResource(0);
+
+ // 先检查是否有自定义背景
+ Bitmap customBackground = null;
+ boolean useCustomBackground = false;
+
+ if (!currentWeather.isEmpty()) {
+ customBackground = BackgroundManager.getBackgroundImage(this, currentWeather);
+ useCustomBackground = (customBackground != null);
+ }
+
+ // 处理动画过渡
+ backgroundImageView.setAlpha(0f);
+
+ if (useCustomBackground) {
+ // 使用自定义背景
+ backgroundImageView.setImageBitmap(customBackground);
+ Log.d(TAG, "使用自定义背景: " + currentWeather);
+ } else {
+ // 使用系统默认背景
+ int backgroundResourceId = Util.getWeatherBackground(weather);
+ backgroundImageView.setImageResource(backgroundResourceId);
+ Log.d(TAG, "使用默认背景: " + backgroundResourceId);
+ }
+
+ // 在黑暗模式下调整图片亮度 - 使用更暗的滤镜
+ if (isDarkMode) {
+ backgroundImageView.setColorFilter(
+ android.graphics.Color.argb(220, 0, 0, 0), // 增加不透明度,使背景更暗
+ android.graphics.PorterDuff.Mode.DARKEN);
+ } else {
+ backgroundImageView.clearColorFilter();
+ }
+
+ // 应用动画效果
+ backgroundImageView.animate()
+ .alpha(1f)
+ .setDuration(500)
+ .start();
+
+ // 保存当前状态
+ String tag = (useCustomBackground ? "custom_" : "") + weather + "_" + isDarkMode;
+ backgroundImageView.setTag(tag);
+ } catch (Exception e) {
+ e.printStackTrace();
+ // 如果发生异常,使用默认背景
+ weatherContainer.setBackgroundResource(0); // 清除背景
+ backgroundImageView.setImageResource(R.drawable.sunny_background);
+ }
+ }
+
+ /**
+ * 根据天气状况调整文字颜色
+ * @param weather 天气状况
+ */
+ private void adjustTextColors(String weather) {
+ // 调用无参数的新方法代替
+ adjustTextColors();
+ }
+
+ /**
+ * 调整文本颜色(带平滑渐变效果)
+ */
+ private void adjustTextColors() {
+ try {
+ // 检查是否为暗黑模式
+ boolean isDarkMode = SettingsManager.isDarkModeEnabled(this);
+
+ // 单独处理输入框,确保在任何模式下都能看清内容
+ if (etCityName != null) {
+ if (isDarkMode) {
+ // 暗黑模式下使用更明亮的文本,以在半透明背景上清晰可见
+ etCityName.setTextColor(0xFFFFFFFF); // 白色
+ etCityName.setHintTextColor(0x80AAAAAA); // 半透明浅灰色提示文本
+ } else {
+ // 普通模式下使用黑色文本
+ etCityName.setTextColor(0xFF000000); // 黑色
+ etCityName.setHintTextColor(0x80666666); // 半透明灰色提示文本
+ }
+ }
+
+ // 获取所有需要调整颜色的文本组件(不包括etCityName)
+ TextView[] textViews = new TextView[] {
+ tvCity, tvWeather, tvTemp, tvWind, tvHumidity,
+ tvForecast1, tvForecast2, tvForecast3, tvForecast4
+ };
+
+ // 文本颜色动画的目标颜色
+ int targetTextColor;
+
+ if (isDarkMode) {
+ // 暗黑模式文字颜色
+ targetTextColor = ContextCompat.getColor(this, R.color.text_color_dark);
+ } else {
+ // 正常模式文字颜色(根据当前天气背景自动调整)
+ if (currentWeather.contains("雨") || currentWeather.contains("阴")) {
+ targetTextColor = 0xFFFFFFFF; // 白色
+ } else if (currentWeather.contains("晴")) {
+ targetTextColor = 0xFF333333; // 深灰色
+ } else {
+ targetTextColor = 0xFFFFFFFF; // 默认白色
+ }
+ }
+
+ // 为每个文本视图应用动画
+ for (TextView tv : textViews) {
+ if (tv != null) {
+ // 获取当前颜色
+ int currentColor = tv.getCurrentTextColor();
+
+ // 如果颜色不同,应用渐变动画
+ if (currentColor != targetTextColor) {
+ ValueAnimator colorAnimation = ValueAnimator.ofObject(
+ new ArgbEvaluator(), currentColor, targetTextColor);
+
+ colorAnimation.setDuration(300);
+ colorAnimation.addUpdateListener(animator -> {
+ if (tv != null && !isFinishing()) {
+ tv.setTextColor((int) animator.getAnimatedValue());
+ }
+ });
+
+ colorAnimation.start();
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.e(TAG, "调整文本颜色时出错: " + e.getMessage());
+
+ // 发生错误时使用备用方案(直接设置颜色)
+ try {
+ // 单独处理输入框
+ if (etCityName != null) {
+ boolean isDarkMode = SettingsManager.isDarkModeEnabled(this);
+ if (isDarkMode) {
+ etCityName.setTextColor(0xFFFFFFFF); // 白色
+ etCityName.setHintTextColor(0x80AAAAAA); // 半透明浅灰色
+ } else {
+ etCityName.setTextColor(0xFF000000); // 黑色
+ etCityName.setHintTextColor(0x80666666); // 半透明灰色
+ }
+ }
+
+ boolean isDarkMode = SettingsManager.isDarkModeEnabled(this);
+ int color = isDarkMode ?
+ ContextCompat.getColor(this, R.color.text_color_dark) : 0xFFFFFFFF;
+
+ // 直接设置颜色(无动画)
+ if (tvCity != null) tvCity.setTextColor(color);
+ if (tvWeather != null) tvWeather.setTextColor(color);
+ if (tvTemp != null) tvTemp.setTextColor(color);
+ if (tvWind != null) tvWind.setTextColor(color);
+ if (tvHumidity != null) tvHumidity.setTextColor(color);
+ if (tvForecast1 != null) tvForecast1.setTextColor(color);
+ if (tvForecast2 != null) tvForecast2.setTextColor(color);
+ if (tvForecast3 != null) tvForecast3.setTextColor(color);
+ if (tvForecast4 != null) tvForecast4.setTextColor(color);
+ } catch (Exception ex) {
+ Log.e(TAG, "备用颜色设置也失败: " + ex.getMessage());
+ }
+ }
+ }
+
+ /**
+ * 设置自动更新
+ */
+ private void setupAutoUpdate() {
+ if (autoUpdateHandler != null && autoUpdateRunnable != null) {
+ // 停止之前的自动更新
+ stopAutoUpdate();
+ }
+
+ autoUpdateRunnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (!currentCity.isEmpty()) {
+ Log.d(TAG, "执行自动更新天气");
+ queryCurrentWeather(currentCity);
+ queryWeatherForecast(currentCity);
+ queryWeatherAlerts(currentCity);
+ lastWeatherUpdateTime = System.currentTimeMillis();
+ }
+
+ // 固定每30分钟更新一次
+ autoUpdateHandler.postDelayed(this, 30 * 60 * 1000);
+ } catch (Exception e) {
+ Log.e(TAG, "自动更新异常: " + e.getMessage(), e);
+ e.printStackTrace();
+ }
+ }
+ };
+
+ // 启动自动更新(首次延迟30分钟)
+ autoUpdateHandler.postDelayed(autoUpdateRunnable, 30 * 60 * 1000);
+ }
+
+ /**
+ * 停止自动更新
+ */
+ private void stopAutoUpdate() {
+ if (autoUpdateRunnable != null) {
+ autoUpdateHandler.removeCallbacks(autoUpdateRunnable);
+ Log.d(TAG, "已停止自动更新");
+ }
+ }
+
+ /**
+ * 加载默认城市
+ */
+ private void loadDefaultCity() {
+ try {
+ // 如果已经有currentCity,不要覆盖它
+ if (!currentCity.isEmpty()) {
+ Log.d(TAG, "保留当前城市: " + currentCity);
+ // 初始化城市选择器
+ setupCitySpinner();
+
+ // 确保城市选择器选中正确的城市,必要时在搜索框显示当前城市
+ boolean cityFound = false;
+ for (int i = 0; i < cityNameList.size(); i++) {
+ if (cityNameList.get(i).equals(currentCity)) {
+ spinnerCities.setSelection(i);
+ cityFound = true;
+ break;
+ }
+ }
+
+ if (!cityFound) {
+ etCityName.setText(currentCity);
+ }
+
+ return;
+ }
+
+ // 获取当前城市
+ City city = CityManager.getCurrentCity(this);
+
+ if (city != null && !city.getName().isEmpty()) {
+ Log.d(TAG, "加载默认城市: " + city.getName());
+
+ // 设置当前城市
+ currentCity = city.getName();
+ } else {
+ // 没有收藏城市,使用北京作为默认城市(但不添加到收藏列表)
+ Log.d(TAG, "没有默认城市,使用北京作为默认城市");
+ currentCity = "北京";
+ }
+
+ // 初始化城市选择器
+ setupCitySpinner();
+
+ // 设置搜索框显示当前城市
+ etCityName.setText(currentCity);
+
+ // 查询当前城市天气
+ queryCurrentWeather(currentCity);
+ queryWeatherForecast(currentCity);
+ queryWeatherAlerts(currentCity);
+ } catch (Exception e) {
+ Log.e(TAG, "加载默认城市失败: " + e.getMessage());
+ // 出现异常时,使用北京作为默认城市
+ currentCity = "北京";
+ etCityName.setText(currentCity);
+ queryCurrentWeather(currentCity);
+ queryWeatherForecast(currentCity);
+ }
+ }
+
+ /**
+ * 分享天气信息到微信好友
+ */
+ private void shareWeatherInfo() {
+ try {
+ if (currentCity.isEmpty() || currentWeather.isEmpty()) {
+ Toast.makeText(this, R.string.no_weather_to_share, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // 构建要分享的文本
+ StringBuilder shareText = new StringBuilder();
+ shareText.append("【").append(currentCity).append("天气】\n");
+ shareText.append("当前天气: ").append(currentWeather).append("\n");
+
+ // 如果有温度信息
+ if (tvTemp != null && tvTemp.getText() != null) {
+ shareText.append(tvTemp.getText()).append("\n");
+ }
+
+ // 如果有风力风向信息
+ if (tvWind != null && tvWind.getText() != null) {
+ shareText.append(tvWind.getText()).append("\n");
+ }
+
+ // 如果有湿度信息
+ if (tvHumidity != null && tvHumidity.getText() != null) {
+ shareText.append(tvHumidity.getText()).append("\n");
+ }
+
+ // 添加未来天气预报
+ shareText.append("\n【未来天气预报】\n");
+ if (tvForecast1 != null && tvForecast1.getText() != null) {
+ shareText.append(tvForecast1.getText()).append("\n");
+ }
+ if (tvForecast2 != null && tvForecast2.getText() != null) {
+ shareText.append(tvForecast2.getText()).append("\n");
+ }
+ if (tvForecast3 != null && tvForecast3.getText() != null) {
+ shareText.append(tvForecast3.getText()).append("\n");
+ }
+ if (tvForecast4 != null && tvForecast4.getText() != null) {
+ shareText.append(tvForecast4.getText()).append("\n");
+ }
+
+ // 添加分享来源标记
+ shareText.append("\n——").append(getString(R.string.share_from));
+
+ // 创建分享intent
+ Intent shareIntent = new Intent();
+ shareIntent.setAction(Intent.ACTION_SEND);
+ shareIntent.putExtra(Intent.EXTRA_TEXT, shareText.toString());
+ shareIntent.setType("text/plain");
+
+ // 检查是否安装了微信
+ if (isWechatInstalled()) {
+ // 如果微信已安装,尝试直接分享到微信
+ shareIntent.setPackage("com.tencent.mm");
+ startActivity(shareIntent);
+ } else {
+ // 如果未安装微信,则弹出系统分享菜单
+ startActivity(Intent.createChooser(shareIntent, getString(R.string.share_title)));
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "分享天气信息时出错: " + e.getMessage(), e);
+ Toast.makeText(this, R.string.share_failed, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ /**
+ * 检查设备是否安装微信
+ * @return 是否安装微信
+ */
+ private boolean isWechatInstalled() {
+ try {
+ getPackageManager().getPackageInfo("com.tencent.mm", 0);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * 初始化预警适配器
+ */
+ private void setupAlertAdapter() {
+ alertAdapter = new AlertAdapter(this, alertList);
+ rvAlerts.setLayoutManager(new LinearLayoutManager(this));
+ rvAlerts.setAdapter(alertAdapter);
+ }
+
+ /**
+ * 查询天气预警信息
+ * @param cityName 城市名称
+ */
+ private void queryWeatherAlerts(String cityName) {
+ AlertService.getWeatherAlerts(cityName, new AlertService.AlertCallback() {
+ @Override
+ public void onSuccess(List alerts) {
+ updateAlertUI(alerts);
+ }
+
+ @Override
+ public void onError(String errorMsg) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Log.e(TAG, "获取天气预警失败: " + errorMsg);
+ // 清空预警列表并隐藏
+ updateAlertUI(new ArrayList<>());
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * 更新天气预警UI
+ * @param alerts 预警列表
+ */
+ private void updateAlertUI(final List alerts) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ alertList.clear();
+ if (alerts != null && !alerts.isEmpty()) {
+ alertList.addAll(alerts);
+ alertContainer.setVisibility(View.VISIBLE);
+ rvAlerts.setVisibility(View.VISIBLE);
+ tvNoAlerts.setVisibility(View.GONE);
+
+ // 根据预警级别变更颜色
+ if (!alerts.isEmpty()) {
+ adjustAlertContainerColor(alerts.get(0).getLevel());
+ }
+
+ Log.d(TAG, "显示 " + alerts.size() + " 条天气预警");
+ } else {
+ // 无预警信息
+ alertContainer.setVisibility(View.VISIBLE);
+ rvAlerts.setVisibility(View.GONE);
+ tvNoAlerts.setVisibility(View.VISIBLE);
+ Log.d(TAG, "没有天气预警信息");
+ }
+ alertAdapter.notifyDataSetChanged();
+ }
+ });
+ }
+
+ /**
+ * 根据预警级别调整预警容器颜色
+ * @param level 预警级别
+ */
+ private void adjustAlertContainerColor(String level) {
+ int colorRes;
+ if (level.contains("红")) {
+ colorRes = R.color.alert_red;
+ } else if (level.contains("橙")) {
+ colorRes = R.color.alert_orange;
+ } else if (level.contains("黄")) {
+ colorRes = R.color.alert_yellow;
+ } else if (level.contains("蓝")) {
+ colorRes = R.color.alert_blue;
+ } else {
+ colorRes = R.color.alert_default;
+ }
+
+ // 使用SettingsManager获取适合当前主题的颜色
+ int color = SettingsManager.getDarkModeColor(this, colorRes);
+
+ // 将颜色转换为半透明(保留原有颜色的RGB值,但设置50%透明度)
+ int alpha = 128; // Alpha值为128表示50%透明度
+ int red = Color.red(color);
+ int green = Color.green(color);
+ int blue = Color.blue(color);
+ int semiTransparentColor = Color.argb(alpha, red, green, blue);
+
+ alertContainer.setBackgroundColor(semiTransparentColor);
+ }
+
+ /**
+ * 在暗黑模式下应用较暗的工具栏颜色
+ */
+ private void applyDarkModeToToolbar() {
+ try {
+ // 调整工具栏颜色 - 使用半透明黑色
+ if (toolbar != null) {
+ toolbar.setBackgroundColor(Color.argb(160, 0, 0, 0)); // 63%不透明度的黑色
+ }
+
+ // 调整状态栏和导航栏颜色
+ getWindow().setStatusBarColor(Color.argb(160, 0, 0, 0)); // 63%不透明度的黑色
+ getWindow().setNavigationBarColor(Color.argb(160, 0, 0, 0)); // 63%不透明度的黑色
+
+ // 如果搜索栏容器可见,也调整其颜色
+ View searchContainer = findViewById(R.id.search_container);
+ if (searchContainer != null) {
+ searchContainer.setBackgroundColor(Color.argb(160, 0, 0, 0)); // 63%不透明度的黑色
+
+ // 调整搜索框内的文字颜色
+ EditText cityNameEditText = findViewById(R.id.cityname);
+ if (cityNameEditText != null) {
+ cityNameEditText.setTextColor(Color.rgb(200, 200, 200)); // 浅灰色文字
+ cityNameEditText.setHintTextColor(Color.rgb(120, 120, 120)); // 更暗的灰色提示文字
+ }
+
+ // 调整搜索按钮文字颜色
+ Button searchButton = findViewById(R.id.search);
+ if (searchButton != null) {
+ searchButton.setTextColor(Color.rgb(200, 200, 200)); // 浅灰色文字
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "应用暗黑模式颜色时出错: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 初始化图片选择器
+ */
+ private void setupImagePicker() {
+ imagePickerLauncher = registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ result -> {
+ if (result.getResultCode() == RESULT_OK && result.getData() != null) {
+ Uri selectedImage = result.getData().getData();
+ if (selectedImage != null) {
+ handleSelectedImage(selectedImage);
+ }
+ }
+ }
+ );
+ }
+
+ /**
+ * 处理所选图片
+ */
+ private void handleSelectedImage(Uri imageUri) {
+ if (currentWeather.isEmpty()) {
+ Toast.makeText(this, "请先查询天气", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // 显示加载对话框
+ android.app.ProgressDialog progressDialog = new android.app.ProgressDialog(this);
+ progressDialog.setMessage("正在保存背景图片...");
+ progressDialog.setCancelable(false);
+ progressDialog.show();
+
+ // 在后台线程保存图片
+ new Thread(() -> {
+ boolean success = BackgroundManager.saveBackgroundImage(this, currentWeather, imageUri);
+
+ runOnUiThread(() -> {
+ progressDialog.dismiss();
+
+ if (success) {
+ Toast.makeText(this, "背景图片已更换", Toast.LENGTH_SHORT).show();
+ // 更新背景
+ updateBackground(currentWeather);
+ } else {
+ Toast.makeText(this, "无法保存背景图片,请重试", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }).start();
+ }
+
+ /**
+ * 打开图片选择器
+ */
+ private void openImagePicker() {
+ try {
+ Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+ imagePickerLauncher.launch(intent);
+ } catch (Exception e) {
+ Log.e(TAG, "打开图片选择器失败: " + e.getMessage(), e);
+ Toast.makeText(this, "无法打开图片选择器", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void setupPermissionLauncher() {
+ requestPermissionLauncher = registerForActivityResult(
+ new ActivityResultContracts.RequestPermission(),
+ isGranted -> {
+ if (isGranted) {
+ // 权限被授予,打开图片选择器
+ Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+ imagePickerLauncher.launch(intent);
+ } else {
+ // 权限被拒绝
+ Toast.makeText(this, "需要存储权限来选择背景图片", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ private void checkAndRequestPermission() {
+ // 先提示用户自定义背景逻辑变更
+ Toast.makeText(this, "自定义背景图片将根据天气类型保存,而非城市名称", Toast.LENGTH_LONG).show();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ // Android 13及以上使用READ_MEDIA_IMAGES权限
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES)
+ != PackageManager.PERMISSION_GRANTED) {
+ requestPermissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES);
+ } else {
+ // 使用正确的Intent打开图片选择器
+ Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+ imagePickerLauncher.launch(intent);
+ }
+ } else {
+ // Android 12及以下使用READ_EXTERNAL_STORAGE权限
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE);
+ } else {
+ // 使用正确的Intent打开图片选择器
+ Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+ imagePickerLauncher.launch(intent);
+ }
+ }
+ }
+
+ /**
+ * 在主题切换后恢复UI状态
+ */
+ private void restoreUIAfterThemeChange() {
+ try {
+ // 确保当前城市显示在输入框中
+ if (!currentCity.isEmpty()) {
+ etCityName.setText(currentCity);
+ } else {
+ // 如果没有当前城市,默认使用北京
+ currentCity = "北京";
+ etCityName.setText(currentCity);
+ }
+
+ // 在城市下拉列表中查找并选中当前城市
+ if (!currentCity.isEmpty() && cityNameList != null && cityNameList.size() > 0) {
+ boolean found = false;
+ for (int i = 0; i < cityNameList.size(); i++) {
+ if (cityNameList.get(i).equals(currentCity)) {
+ spinnerCities.setSelection(i);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ Log.d(TAG, "当前城市 " + currentCity + " 不在收藏列表中");
+ }
+ }
+
+ // 如果收藏城市列表为空,隐藏下拉菜单
+ if (cityNameList.isEmpty()) {
+ spinnerCities.setVisibility(View.GONE);
+ } else {
+ spinnerCities.setVisibility(View.VISIBLE);
+ }
+
+ // 更新收藏按钮状态
+ updateFavoriteButtonState();
+
+ // 如果已经查询过天气,强制刷新UI
+ if (!currentCity.isEmpty() && !currentWeather.isEmpty()) {
+ Log.d(TAG, "主题切换后恢复城市 " + currentCity + " 的天气显示");
+
+ // 根据是否为暗黑模式更新UI样式
+ boolean isDarkMode = SettingsManager.isDarkModeEnabled(this);
+ if (isDarkMode) {
+ applyDarkModeToToolbar();
+ } else {
+ if (toolbar != null) {
+ toolbar.setBackgroundColor(Color.argb(96, 0, 0, 0));
+ }
+ View searchContainer = findViewById(R.id.search_container);
+ if (searchContainer != null) {
+ searchContainer.setBackgroundColor(Color.argb(96, 0, 0, 0));
+ }
+ }
+
+ // 更新背景和文字颜色
+ updateBackground(currentWeather);
+ adjustTextColors(currentWeather);
+ } else if (!currentCity.isEmpty()) {
+ // 如果有城市但没有天气数据,查询天气
+ Log.d(TAG, "主题切换后查询城市 " + currentCity + " 的天气");
+ queryCurrentWeather(currentCity);
+ queryWeatherForecast(currentCity);
+ queryWeatherAlerts(currentCity);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "恢复UI状态出错: " + e.getMessage(), e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/model/City.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/model/City.java
new file mode 100644
index 0000000..58dd478
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/model/City.java
@@ -0,0 +1,54 @@
+package com.example.myapplication.model;
+
+/**
+ * 城市信息数据模型
+ */
+public class City {
+ private int id; // 城市ID
+ private String name; // 城市名称
+ private boolean isSelected; // 是否选中
+
+ // 构造方法
+ public City() {
+ }
+
+ public City(int id, String name, boolean isSelected) {
+ this.id = id;
+ this.name = name;
+ this.isSelected = isSelected;
+ }
+
+ // getter和setter方法
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public boolean isSelected() {
+ return isSelected;
+ }
+
+ public void setSelected(boolean selected) {
+ isSelected = selected;
+ }
+
+ @Override
+ public String toString() {
+ return "City{" +
+ "id=" + id +
+ ", name='" + name + '\'' +
+ ", isSelected=" + isSelected +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/model/Forecast.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/model/Forecast.java
new file mode 100644
index 0000000..b4c41a0
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/model/Forecast.java
@@ -0,0 +1,141 @@
+package com.example.myapplication.model;
+
+/**
+ * 天气预报数据模型
+ */
+public class Forecast {
+ private String date; // 预报日期
+ private String week; // 星期
+ private String dayWeather; // 白天天气
+ private String nightWeather; // 夜间天气
+ private int dayTemperature; // 白天温度
+ private int nightTemperature; // 夜间温度
+ private String dayWind; // 白天风向
+ private String nightWind; // 夜间风向
+ private String dayPower; // 白天风力
+ private String nightPower; // 夜间风力
+
+ // 构造方法
+ public Forecast() {
+ }
+
+ public Forecast(String date, String week, String dayWeather, String nightWeather,
+ int dayTemperature, int nightTemperature, String dayWind,
+ String nightWind, String dayPower, String nightPower) {
+ this.date = date;
+ this.week = week;
+ this.dayWeather = dayWeather;
+ this.nightWeather = nightWeather;
+ this.dayTemperature = dayTemperature;
+ this.nightTemperature = nightTemperature;
+ this.dayWind = dayWind;
+ this.nightWind = nightWind;
+ this.dayPower = dayPower;
+ this.nightPower = nightPower;
+ }
+
+ // getter和setter方法
+ public String getDate() {
+ return date;
+ }
+
+ public void setDate(String date) {
+ this.date = date;
+ }
+
+ public String getWeek() {
+ return week;
+ }
+
+ public void setWeek(String week) {
+ this.week = week;
+ }
+
+ public String getDayWeather() {
+ return dayWeather;
+ }
+
+ public void setDayWeather(String dayWeather) {
+ this.dayWeather = dayWeather;
+ }
+
+ public String getNightWeather() {
+ return nightWeather;
+ }
+
+ public void setNightWeather(String nightWeather) {
+ this.nightWeather = nightWeather;
+ }
+
+ public int getDayTemperature() {
+ return dayTemperature;
+ }
+
+ public void setDayTemperature(int dayTemperature) {
+ this.dayTemperature = dayTemperature;
+ }
+
+ public int getNightTemperature() {
+ return nightTemperature;
+ }
+
+ public void setNightTemperature(int nightTemperature) {
+ this.nightTemperature = nightTemperature;
+ }
+
+ public String getDayWind() {
+ return dayWind;
+ }
+
+ public void setDayWind(String dayWind) {
+ this.dayWind = dayWind;
+ }
+
+ public String getNightWind() {
+ return nightWind;
+ }
+
+ public void setNightWind(String nightWind) {
+ this.nightWind = nightWind;
+ }
+
+ public String getDayPower() {
+ return dayPower;
+ }
+
+ public void setDayPower(String dayPower) {
+ this.dayPower = dayPower;
+ }
+
+ public String getNightPower() {
+ return nightPower;
+ }
+
+ public void setNightPower(String nightPower) {
+ this.nightPower = nightPower;
+ }
+
+ @Override
+ public String toString() {
+ return "Forecast{" +
+ "date='" + date + '\'' +
+ ", week='" + week + '\'' +
+ ", dayWeather='" + dayWeather + '\'' +
+ ", nightWeather='" + nightWeather + '\'' +
+ ", dayTemperature=" + dayTemperature +
+ ", nightTemperature=" + nightTemperature +
+ ", dayWind='" + dayWind + '\'' +
+ ", nightWind='" + nightWind + '\'' +
+ ", dayPower='" + dayPower + '\'' +
+ ", nightPower='" + nightPower + '\'' +
+ '}';
+ }
+
+ /**
+ * 获取温度范围字符串表示
+ * @return 格式如"25°C ~ 18°C"
+ */
+ public String getTemperatureRange() {
+ return dayTemperature + "°C ~ " + nightTemperature + "°C";
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/model/WeatherAlert.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/model/WeatherAlert.java
new file mode 100644
index 0000000..861e414
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/model/WeatherAlert.java
@@ -0,0 +1,88 @@
+package com.example.myapplication.model;
+
+/**
+ * 天气预警信息数据模型
+ */
+public class WeatherAlert {
+ private String title; // 预警标题
+ private String content; // 预警内容
+ private String level; // 预警级别 (红色,橙色,黄色,蓝色)
+ private String type; // 预警类型
+ private String publishTime; // 发布时间
+ private String city; // 所属城市
+
+ // 构造方法
+ public WeatherAlert() {
+ }
+
+ public WeatherAlert(String title, String content, String level, String type,
+ String publishTime, String city) {
+ this.title = title;
+ this.content = content;
+ this.level = level;
+ this.type = type;
+ this.publishTime = publishTime;
+ this.city = city;
+ }
+
+ // getter和setter方法
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public String getLevel() {
+ return level;
+ }
+
+ public void setLevel(String level) {
+ this.level = level;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getPublishTime() {
+ return publishTime;
+ }
+
+ public void setPublishTime(String publishTime) {
+ this.publishTime = publishTime;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ @Override
+ public String toString() {
+ return "WeatherAlert{" +
+ "title='" + title + '\'' +
+ ", content='" + content + '\'' +
+ ", level='" + level + '\'' +
+ ", type='" + type + '\'' +
+ ", publishTime='" + publishTime + '\'' +
+ ", city='" + city + '\'' +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/model/WeatherInfo.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/model/WeatherInfo.java
new file mode 100644
index 0000000..9648c27
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/model/WeatherInfo.java
@@ -0,0 +1,100 @@
+package com.example.myapplication.model;
+
+/**
+ * 天气信息数据模型
+ */
+public class WeatherInfo {
+ private String city; // 城市名称
+ private String weather; // 天气状况
+ private int temperature; // 温度
+ private String windDirection; // 风向
+ private String windPower; // 风力
+ private int humidity; // 湿度
+ private String reportTime; // 发布时间
+
+ // 构造方法
+ public WeatherInfo() {
+ }
+
+ public WeatherInfo(String city, String weather, int temperature,
+ String windDirection, String windPower, int humidity,
+ String reportTime) {
+ this.city = city;
+ this.weather = weather;
+ this.temperature = temperature;
+ this.windDirection = windDirection;
+ this.windPower = windPower;
+ this.humidity = humidity;
+ this.reportTime = reportTime;
+ }
+
+ // getter和setter方法
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getWeather() {
+ return weather;
+ }
+
+ public void setWeather(String weather) {
+ this.weather = weather;
+ }
+
+ public int getTemperature() {
+ return temperature;
+ }
+
+ public void setTemperature(int temperature) {
+ this.temperature = temperature;
+ }
+
+ public String getWindDirection() {
+ return windDirection;
+ }
+
+ public void setWindDirection(String windDirection) {
+ this.windDirection = windDirection;
+ }
+
+ public String getWindPower() {
+ return windPower;
+ }
+
+ public void setWindPower(String windPower) {
+ this.windPower = windPower;
+ }
+
+ public int getHumidity() {
+ return humidity;
+ }
+
+ public void setHumidity(int humidity) {
+ this.humidity = humidity;
+ }
+
+ public String getReportTime() {
+ return reportTime;
+ }
+
+ public void setReportTime(String reportTime) {
+ this.reportTime = reportTime;
+ }
+
+ @Override
+ public String toString() {
+ return "WeatherInfo{" +
+ "city='" + city + '\'' +
+ ", weather='" + weather + '\'' +
+ ", temperature=" + temperature +
+ ", windDirection='" + windDirection + '\'' +
+ ", windPower='" + windPower + '\'' +
+ ", humidity=" + humidity +
+ ", reportTime='" + reportTime + '\'' +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/service/AlertService.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/service/AlertService.java
new file mode 100644
index 0000000..0c7b62b
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/service/AlertService.java
@@ -0,0 +1,190 @@
+package com.example.myapplication.service;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.example.myapplication.model.WeatherAlert;
+import com.example.myapplication.utils.HttpUtils;
+import com.example.myapplication.utils.WeatherApiConstants;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * 天气预警服务类
+ */
+public class AlertService {
+ private static final String TAG = "AlertService";
+
+ // 创建一个单线程执行器,用于网络请求
+ private static final Executor executor = Executors.newSingleThreadExecutor();
+ // 创建一个主线程Handler,用于更新UI
+ private static final Handler mainHandler = new Handler(Looper.getMainLooper());
+
+ /**
+ * 获取天气预警信息
+ * @param cityName 城市名称
+ * @param callback 回调接口
+ */
+ public static void getWeatherAlerts(final String cityName, final AlertCallback callback) {
+ Log.d(TAG, "开始获取天气预警: " + cityName);
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ String url = buildAlertUrl(cityName);
+ Log.d(TAG, "预警请求URL: " + url);
+
+ if (url == null) {
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError("构建预警URL失败");
+ }
+ });
+ return;
+ }
+
+ String jsonResponse = HttpUtils.sendHttpRequest(url);
+ if (jsonResponse == null || jsonResponse.isEmpty()) {
+ Log.e(TAG, "返回的预警JSON为空");
+
+ // 模拟空结果,因为天气预警可能没有数据
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onSuccess(new ArrayList<>());
+ }
+ });
+ return;
+ }
+
+ Log.d(TAG, "获取的预警JSON: " + (jsonResponse.length() > 200 ? jsonResponse.substring(0, 200) + "..." : jsonResponse));
+ final List result = parseAlerts(jsonResponse, cityName);
+
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (result != null) {
+ Log.d(TAG, "成功解析天气预警,数量: " + result.size());
+ callback.onSuccess(result);
+ } else {
+ Log.e(TAG, "解析天气预警失败");
+ callback.onError("获取天气预警信息失败");
+ }
+ }
+ });
+ } catch (final Exception e) {
+ Log.e(TAG, "获取天气预警异常: " + e.getMessage(), e);
+ e.printStackTrace();
+
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError("网络请求错误: " + e.getMessage());
+ }
+ });
+ }
+ }
+ });
+ }
+
+ /**
+ * 解析天气预警JSON数据
+ * @param jsonData JSON字符串
+ * @param cityName 城市名称
+ * @return 预警信息列表
+ */
+ private static List parseAlerts(String jsonData, String cityName) {
+ if (jsonData == null || jsonData.isEmpty()) {
+ Log.e(TAG, "解析天气预警数据失败: JSON数据为空");
+ return new ArrayList<>();
+ }
+
+ try {
+ List alertList = new ArrayList<>();
+ JSONObject jsonObject = new JSONObject(jsonData);
+ String status = jsonObject.getString("status");
+
+ if ("1".equals(status)) {
+ // 检查是否存在alerts节点
+ if (jsonObject.has("alerts") && !jsonObject.isNull("alerts")) {
+ JSONArray alerts = jsonObject.getJSONArray("alerts");
+
+ for (int i = 0; i < alerts.length(); i++) {
+ JSONObject alertObj = alerts.getJSONObject(i);
+
+ String title = alertObj.optString("title", "");
+ String content = alertObj.optString("content", "");
+ String level = alertObj.optString("level", "");
+ String type = alertObj.optString("type", "");
+ String publishTime = alertObj.optString("pub_time", "");
+
+ WeatherAlert alert = new WeatherAlert(
+ title, content, level, type, publishTime, cityName);
+ alertList.add(alert);
+
+ Log.d(TAG, "解析到预警: " + alert.toString());
+ }
+ } else {
+ Log.d(TAG, "当前没有预警信息");
+ }
+ } else {
+ Log.e(TAG, "获取预警信息失败,状态码: " + status);
+ }
+
+ return alertList;
+ } catch (JSONException e) {
+ Log.e(TAG, "解析预警JSON失败: " + e.getMessage(), e);
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 构建天气预警查询URL
+ * @param cityName 城市名称
+ * @return 构建好的URL字符串
+ */
+ private static String buildAlertUrl(String cityName) {
+ StringBuilder urlBuilder = new StringBuilder(WeatherApiConstants.WEATHER_ALERT_URL);
+
+ try {
+ // 获取城市编码
+ String cityCode = com.example.myapplication.utils.Util.getCityCode(cityName);
+ Log.d(TAG, "预警API城市: " + cityName + ", 城市编码: " + cityCode);
+
+ urlBuilder.append("?").append(WeatherApiConstants.PARAM_KEY).append("=")
+ .append(WeatherApiConstants.AMAP_WEB_API_KEY);
+ urlBuilder.append("&").append(WeatherApiConstants.PARAM_CITY).append("=")
+ .append(java.net.URLEncoder.encode(cityCode, "UTF-8"));
+
+ // 返回JSON格式数据
+ urlBuilder.append("&").append(WeatherApiConstants.PARAM_OUTPUT).append("=")
+ .append(WeatherApiConstants.VALUE_OUTPUT_JSON);
+
+ String finalUrl = urlBuilder.toString();
+ Log.d(TAG, "构建的预警URL: " + finalUrl);
+ return finalUrl;
+ } catch (Exception e) {
+ Log.e(TAG, "构建预警URL时出错: " + e.getMessage(), e);
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 预警回调接口
+ */
+ public interface AlertCallback {
+ void onSuccess(List alertList);
+ void onError(String errorMsg);
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/service/ForecastService.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/service/ForecastService.java
new file mode 100644
index 0000000..6c5e312
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/service/ForecastService.java
@@ -0,0 +1,223 @@
+package com.example.myapplication.service;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.example.myapplication.model.Forecast;
+import com.example.myapplication.utils.HttpUtils;
+import com.example.myapplication.utils.WeatherApiConstants;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * 天气预报服务类
+ */
+public class ForecastService {
+ private static final String TAG = "ForecastService";
+
+ // 创建一个单线程执行器,用于网络请求
+ private static final Executor executor = Executors.newSingleThreadExecutor();
+ // 创建一个主线程Handler,用于更新UI
+ private static final Handler mainHandler = new Handler(Looper.getMainLooper());
+
+ /**
+ * 获取天气预报
+ * @param cityName 城市名称
+ * @param callback 回调接口
+ */
+ public static void getWeatherForecast(final String cityName, final ForecastCallback callback) {
+ Log.d(TAG, "开始获取天气预报: " + cityName);
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ String url = HttpUtils.buildWeatherUrl(cityName, true);
+ Log.d(TAG, "请求URL: " + url);
+
+ if (url == null) {
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError("构建URL失败");
+ }
+ });
+ return;
+ }
+
+ String jsonResponse = HttpUtils.sendHttpRequest(url);
+ if (jsonResponse == null || jsonResponse.isEmpty()) {
+ Log.e(TAG, "返回的JSON为空,尝试使用备用数据");
+
+ // 尝试使用备用方案获取数据
+ jsonResponse = HttpUtils.useBackupForecastApi(cityName);
+
+ if (jsonResponse == null || jsonResponse.isEmpty()) {
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError("获取天气预报失败:所有渠道均无响应");
+ }
+ });
+ return;
+ } else {
+ Log.d(TAG, "成功从备用渠道获取天气预报数据");
+ }
+ }
+
+ Log.d(TAG, "获取的JSON: " + (jsonResponse.length() > 200 ? jsonResponse.substring(0, 200) + "..." : jsonResponse));
+ final List result = parseForecastWeather(jsonResponse);
+
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (result != null && !result.isEmpty()) {
+ Log.d(TAG, "成功解析天气预报,共 " + result.size() + " 条");
+ callback.onSuccess(result);
+ } else {
+ Log.e(TAG, "解析天气预报失败或结果为空");
+ callback.onError("解析天气预报失败");
+ }
+ }
+ });
+ } catch (final Exception e) {
+ Log.e(TAG, "获取天气预报异常: " + e.getMessage(), e);
+ e.printStackTrace();
+
+ // 尝试使用备用方案
+ try {
+ Log.d(TAG, "尝试使用备用方案获取天气预报");
+ final String backupResponse = HttpUtils.useBackupForecastApi(cityName);
+ if (backupResponse != null && !backupResponse.isEmpty()) {
+ final List backupResult = parseForecastWeather(backupResponse);
+
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (backupResult != null && !backupResult.isEmpty()) {
+ Log.d(TAG, "成功从备用方案获取天气预报数据");
+ callback.onSuccess(backupResult);
+ } else {
+ callback.onError("所有获取天气预报的尝试均失败");
+ }
+ }
+ });
+ return;
+ }
+ } catch (Exception backupEx) {
+ Log.e(TAG, "备用方案也失败: " + backupEx.getMessage(), backupEx);
+ }
+
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError("网络请求错误: " + e.getMessage());
+ }
+ });
+ }
+ }
+ });
+ }
+
+ /**
+ * 解析天气预报JSON数据
+ * @param jsonData JSON字符串
+ * @return 天气预报列表
+ */
+ private static List parseForecastWeather(String jsonData) {
+ List forecastList = new ArrayList<>();
+
+ if (jsonData == null || jsonData.isEmpty()) {
+ return forecastList;
+ }
+
+ try {
+ JSONObject jsonObject = new JSONObject(jsonData);
+ String status = jsonObject.getString("status");
+
+ if ("1".equals(status)) {
+ JSONArray forecasts = jsonObject.getJSONArray("forecasts");
+ if (forecasts.length() > 0) {
+ JSONObject forecastData = forecasts.getJSONObject(0);
+ JSONArray casts = forecastData.getJSONArray("casts");
+
+ // 获取当前日期,用于计算星期几
+ Calendar calendar = Calendar.getInstance();
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);
+
+ String[] weekDays = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
+
+ for (int i = 0; i < casts.length(); i++) {
+ JSONObject castData = casts.getJSONObject(i);
+
+ String date = castData.getString("date");
+ String dayWeather = castData.getString("dayweather");
+ String nightWeather = castData.getString("nightweather");
+ int dayTemp = castData.getInt("daytemp");
+ int nightTemp = castData.getInt("nighttemp");
+ String dayWind = castData.getString("daywind");
+ String nightWind = castData.getString("nightwind");
+ String dayPower = castData.getString("daypower");
+ String nightPower = castData.getString("nightpower");
+
+ // 计算星期几
+ String week;
+ try {
+ Date forecastDate = sdf.parse(date);
+ calendar.setTime(forecastDate);
+ int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1;
+ week = weekDays[dayOfWeek];
+ } catch (Exception e) {
+ // 解析日期失败,使用默认值
+ week = i == 0 ? "今天" : i == 1 ? "明天" : i == 2 ? "后天" : "未知";
+ }
+
+ Forecast forecast = new Forecast(
+ date,
+ week,
+ dayWeather,
+ nightWeather,
+ dayTemp,
+ nightTemp,
+ dayWind,
+ nightWind,
+ dayPower,
+ nightPower
+ );
+
+ forecastList.add(forecast);
+ }
+ }
+ } else {
+ // 返回错误信息
+ System.out.println("API返回错误状态码:" + status);
+ if (jsonObject.has("info")) {
+ System.out.println("错误信息:" + jsonObject.getString("info"));
+ }
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ return forecastList;
+ }
+
+ /**
+ * 天气预报回调接口
+ */
+ public interface ForecastCallback {
+ void onSuccess(List forecastList);
+ void onError(String errorMsg);
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/service/WeatherService.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/service/WeatherService.java
new file mode 100644
index 0000000..57f68c1
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/service/WeatherService.java
@@ -0,0 +1,295 @@
+package com.example.myapplication.service;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.example.myapplication.model.WeatherInfo;
+import com.example.myapplication.utils.HttpUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * 天气服务类
+ */
+public class WeatherService {
+ private static final String TAG = "WeatherService";
+
+ // 创建一个单线程执行器,用于网络请求
+ private static final Executor executor = Executors.newSingleThreadExecutor();
+ // 创建一个主线程Handler,用于更新UI
+ private static final Handler mainHandler = new Handler(Looper.getMainLooper());
+
+ /**
+ * 获取当前天气
+ * @param cityName 城市名称
+ * @param callback 回调接口
+ */
+ public static void getCurrentWeather(final String cityName, final WeatherCallback callback) {
+ Log.d(TAG, "开始获取当前天气: " + cityName);
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ String url = HttpUtils.buildWeatherUrl(cityName, false);
+ Log.d(TAG, "请求URL: " + url);
+
+ if (url == null) {
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError("构建URL失败");
+ }
+ });
+ return;
+ }
+
+ String jsonResponse = HttpUtils.sendHttpRequest(url);
+ if (jsonResponse == null || jsonResponse.isEmpty()) {
+ Log.e(TAG, "返回的JSON为空,尝试使用备用数据");
+
+ // 尝试使用备用方案获取数据
+ jsonResponse = HttpUtils.useBackupWeatherApi(cityName);
+
+ if (jsonResponse == null || jsonResponse.isEmpty()) {
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError("获取数据失败,所有渠道均无响应");
+ }
+ });
+ return;
+ } else {
+ Log.d(TAG, "成功从备用渠道获取数据");
+ }
+ }
+
+ Log.d(TAG, "获取的JSON: " + (jsonResponse.length() > 200 ? jsonResponse.substring(0, 200) + "..." : jsonResponse));
+ final WeatherInfo result = parseCurrentWeather(jsonResponse);
+
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (result != null) {
+ Log.d(TAG, "成功解析天气: " + result.toString());
+ callback.onSuccess(result);
+ } else {
+ Log.e(TAG, "解析天气失败");
+ callback.onError("获取天气信息失败");
+ }
+ }
+ });
+ } catch (final Exception e) {
+ Log.e(TAG, "获取当前天气异常: " + e.getMessage(), e);
+ e.printStackTrace();
+
+ // 尝试使用备用方案
+ try {
+ Log.d(TAG, "尝试使用备用方案获取天气数据");
+ final String backupResponse = HttpUtils.useBackupWeatherApi(cityName);
+ if (backupResponse != null && !backupResponse.isEmpty()) {
+ final WeatherInfo backupResult = parseCurrentWeather(backupResponse);
+
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (backupResult != null) {
+ Log.d(TAG, "成功从备用方案获取天气数据");
+ callback.onSuccess(backupResult);
+ } else {
+ callback.onError("所有获取天气数据的尝试均失败");
+ }
+ }
+ });
+ return;
+ }
+ } catch (Exception backupEx) {
+ Log.e(TAG, "备用方案也失败: " + backupEx.getMessage(), backupEx);
+ }
+
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError("网络请求错误: " + e.getMessage());
+ }
+ });
+ }
+ }
+ });
+ }
+
+ /**
+ * 获取天气预报
+ * @param cityName 城市名称
+ * @param callback 回调接口
+ */
+ public static void getWeatherForecast(final String cityName, final ForecastCallback callback) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ String url = HttpUtils.buildWeatherUrl(cityName, true);
+ String jsonResponse = HttpUtils.sendHttpRequest(url);
+ final List result = parseForecastWeather(jsonResponse);
+
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (result != null && !result.isEmpty()) {
+ callback.onSuccess(result);
+ } else {
+ callback.onError("获取天气预报失败");
+ }
+ }
+ });
+ } catch (final Exception e) {
+ e.printStackTrace();
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError("网络请求错误: " + e.getMessage());
+ }
+ });
+ }
+ }
+ });
+ }
+
+ /**
+ * 解析当前天气JSON数据
+ * @param jsonData JSON字符串
+ * @return 天气信息对象
+ */
+ private static WeatherInfo parseCurrentWeather(String jsonData) {
+ if (jsonData == null || jsonData.isEmpty()) {
+ Log.e(TAG, "解析当前天气数据失败: JSON数据为空");
+ return null;
+ }
+
+ try {
+ JSONObject jsonObject = new JSONObject(jsonData);
+ String status = jsonObject.getString("status");
+
+ if ("1".equals(status)) {
+ JSONArray lives = jsonObject.getJSONArray("lives");
+ if (lives.length() > 0) {
+ JSONObject liveData = lives.getJSONObject(0);
+
+ String city = liveData.getString("city");
+ String weather = liveData.getString("weather");
+ int temperature = liveData.getInt("temperature");
+ String windDirection = liveData.getString("winddirection");
+ String windPower = liveData.getString("windpower");
+ int humidity = liveData.getInt("humidity");
+ String reportTime = liveData.getString("reporttime");
+
+ Log.d(TAG, "成功解析城市: " + city + ", 天气: " + weather +
+ ", 温度: " + temperature + "°C, 风向: " + windDirection +
+ ", 风力: " + windPower + ", 湿度: " + humidity + "%, 报告时间: " + reportTime);
+
+ return new WeatherInfo(city, weather, temperature,
+ windDirection, windPower, humidity, reportTime);
+ } else {
+ Log.e(TAG, "lives数组为空");
+ }
+ } else {
+ // 返回错误信息
+ Log.e(TAG, "API返回错误状态码: " + status);
+ if (jsonObject.has("info")) {
+ Log.e(TAG, "错误信息: " + jsonObject.getString("info"));
+ }
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "解析JSON时出错: " + e.getMessage(), e);
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ /**
+ * 解析天气预报JSON数据
+ * @param jsonData JSON字符串
+ * @return 天气预报列表
+ */
+ private static List parseForecastWeather(String jsonData) {
+ List forecastList = new ArrayList<>();
+
+ if (jsonData == null || jsonData.isEmpty()) {
+ return forecastList;
+ }
+
+ try {
+ JSONObject jsonObject = new JSONObject(jsonData);
+ String status = jsonObject.getString("status");
+
+ if ("1".equals(status)) {
+ JSONArray forecasts = jsonObject.getJSONArray("forecasts");
+ if (forecasts.length() > 0) {
+ JSONObject forecastData = forecasts.getJSONObject(0);
+ JSONArray casts = forecastData.getJSONArray("casts");
+
+ for (int i = 0; i < casts.length(); i++) {
+ JSONObject castData = casts.getJSONObject(i);
+
+ String date = castData.getString("date");
+ String dayWeather = castData.getString("dayweather");
+ String nightWeather = castData.getString("nightweather");
+ int dayTemp = castData.getInt("daytemp");
+ int nightTemp = castData.getInt("nighttemp");
+ String dayWind = castData.getString("daywind");
+ String nightWind = castData.getString("nightwind");
+ String dayPower = castData.getString("daypower");
+ String nightPower = castData.getString("nightpower");
+
+ // 使用白天的天气信息作为主要显示
+ WeatherInfo forecast = new WeatherInfo(
+ forecastData.getString("city"),
+ dayWeather,
+ dayTemp, // 只使用白天温度
+ dayWind,
+ dayPower,
+ 0, // 预报中没有湿度数据
+ date
+ );
+
+ forecastList.add(forecast);
+ }
+ }
+ } else {
+ // 返回错误信息
+ System.out.println("API返回错误状态码:" + status);
+ if (jsonObject.has("info")) {
+ System.out.println("错误信息:" + jsonObject.getString("info"));
+ }
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ return forecastList;
+ }
+
+ /**
+ * 当前天气回调接口
+ */
+ public interface WeatherCallback {
+ void onSuccess(WeatherInfo weatherInfo);
+ void onError(String errorMsg);
+ }
+
+ /**
+ * 天气预报回调接口
+ */
+ public interface ForecastCallback {
+ void onSuccess(List forecastList);
+ void onError(String errorMsg);
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/ui/AlertAdapter.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/ui/AlertAdapter.java
new file mode 100644
index 0000000..d8e34e1
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/ui/AlertAdapter.java
@@ -0,0 +1,135 @@
+package com.example.myapplication.ui;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.example.myapplication.R;
+import com.example.myapplication.model.WeatherAlert;
+import com.example.myapplication.utils.SettingsManager;
+
+import java.util.List;
+
+/**
+ * 天气预警适配器
+ */
+public class AlertAdapter extends RecyclerView.Adapter {
+
+ private Context context;
+ private List alertList;
+
+ public AlertAdapter(Context context, List alertList) {
+ this.context = context;
+ this.alertList = alertList;
+ }
+
+ @NonNull
+ @Override
+ public AlertViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_alert, parent, false);
+ return new AlertViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull AlertViewHolder holder, int position) {
+ WeatherAlert alert = alertList.get(position);
+
+ // 设置预警标题
+ holder.tvAlertTitle.setText(alert.getTitle());
+
+ // 设置预警内容
+ holder.tvAlertContent.setText(alert.getContent());
+
+ // 设置预警时间
+ holder.tvAlertTime.setText(alert.getPublishTime());
+
+ // 设置预警级别和图标
+ String level = alert.getLevel();
+ setAlertLevelIndicator(holder, level);
+
+ // 设置点击事件
+ holder.itemView.setOnClickListener(v -> {
+ Toast.makeText(context, alert.getTitle() + "\n" + alert.getContent(),
+ Toast.LENGTH_LONG).show();
+ });
+ }
+
+ /**
+ * 根据预警级别设置不同的颜色和图标
+ */
+ private void setAlertLevelIndicator(AlertViewHolder holder, String level) {
+ int colorRes;
+ int iconRes;
+
+ if (level.contains("红")) {
+ colorRes = R.color.alert_red;
+ iconRes = R.drawable.ic_alert_red;
+ } else if (level.contains("橙")) {
+ colorRes = R.color.alert_orange;
+ iconRes = R.drawable.ic_alert_orange;
+ } else if (level.contains("黄")) {
+ colorRes = R.color.alert_yellow;
+ iconRes = R.drawable.ic_alert_yellow;
+ } else if (level.contains("蓝")) {
+ colorRes = R.color.alert_blue;
+ iconRes = R.drawable.ic_alert_blue;
+ } else {
+ colorRes = R.color.alert_default;
+ iconRes = R.drawable.ic_alert_default;
+ }
+
+ // 根据是否为暗黑模式获取适当的颜色
+ int color = SettingsManager.getDarkModeColor(context, colorRes);
+
+ // 将颜色转换为80%不透明度(与主预警容器保持一致)
+ int alpha = 204; // Alpha值为204表示80%不透明度
+ int red = android.graphics.Color.red(color);
+ int green = android.graphics.Color.green(color);
+ int blue = android.graphics.Color.blue(color);
+ int semiTransparentColor = android.graphics.Color.argb(alpha, red, green, blue);
+
+ holder.vAlertLevel.setBackgroundColor(semiTransparentColor);
+
+ // 设置图标(如果有图标资源)
+ if (iconRes != 0) {
+ holder.ivAlertIcon.setImageResource(iconRes);
+ holder.ivAlertIcon.setVisibility(View.VISIBLE);
+ } else {
+ holder.ivAlertIcon.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return alertList == null ? 0 : alertList.size();
+ }
+
+ /**
+ * 预警ViewHolder
+ */
+ public static class AlertViewHolder extends RecyclerView.ViewHolder {
+ View vAlertLevel; // 预警级别颜色指示器
+ ImageView ivAlertIcon; // 预警图标
+ TextView tvAlertTitle; // 预警标题
+ TextView tvAlertContent; // 预警内容
+ TextView tvAlertTime; // 预警时间
+
+ public AlertViewHolder(@NonNull View itemView) {
+ super(itemView);
+ vAlertLevel = itemView.findViewById(R.id.vAlertLevel);
+ ivAlertIcon = itemView.findViewById(R.id.ivAlertIcon);
+ tvAlertTitle = itemView.findViewById(R.id.tvAlertTitle);
+ tvAlertContent = itemView.findViewById(R.id.tvAlertContent);
+ tvAlertTime = itemView.findViewById(R.id.tvAlertTime);
+ }
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/ui/theme/Color.kt b/MinimalistWeather/app/src/main/java/com/example/myapplication/ui/theme/Color.kt
new file mode 100644
index 0000000..1ce3e5d
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/ui/theme/Color.kt
@@ -0,0 +1,11 @@
+package com.example.myapplication.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/ui/theme/Theme.kt b/MinimalistWeather/app/src/main/java/com/example/myapplication/ui/theme/Theme.kt
new file mode 100644
index 0000000..174f73f
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/ui/theme/Theme.kt
@@ -0,0 +1,58 @@
+package com.example.myapplication.ui.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+
+private val DarkColorScheme = darkColorScheme(
+ primary = Purple80,
+ secondary = PurpleGrey80,
+ tertiary = Pink80
+)
+
+private val LightColorScheme = lightColorScheme(
+ primary = Purple40,
+ secondary = PurpleGrey40,
+ tertiary = Pink40
+
+ /* Other default colors to override
+ background = Color(0xFFFFFBFE),
+ surface = Color(0xFFFFFBFE),
+ onPrimary = Color.White,
+ onSecondary = Color.White,
+ onTertiary = Color.White,
+ onBackground = Color(0xFF1C1B1F),
+ onSurface = Color(0xFF1C1B1F),
+ */
+)
+
+@Composable
+fun MyApplicationTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ // Dynamic color is available on Android 12+
+ dynamicColor: Boolean = true,
+ content: @Composable () -> Unit
+) {
+ val colorScheme = when {
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ val context = LocalContext.current
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ }
+
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
+ }
+
+ MaterialTheme(
+ colorScheme = colorScheme,
+ typography = Typography,
+ content = content
+ )
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/ui/theme/Type.kt b/MinimalistWeather/app/src/main/java/com/example/myapplication/ui/theme/Type.kt
new file mode 100644
index 0000000..64846a0
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/ui/theme/Type.kt
@@ -0,0 +1,34 @@
+package com.example.myapplication.ui.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ bodyLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp
+ )
+ /* Other default text styles to override
+ titleLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.sp
+ ),
+ labelSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 11.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp
+ )
+ */
+)
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/APITester.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/APITester.java
new file mode 100644
index 0000000..3f9b186
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/APITester.java
@@ -0,0 +1,84 @@
+package com.example.myapplication.utils;
+
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * API测试工具类
+ * 用于在应用启动时测试API是否正常工作
+ */
+public class APITester {
+ private static final String TAG = "APITester";
+
+ /**
+ * 测试天气API是否能正常访问
+ * @return 测试结果
+ */
+ public static boolean testWeatherAPI() {
+ try {
+ Log.d(TAG, "开始测试天气API");
+ Log.d(TAG, "使用的API密钥: " + WeatherApiConstants.AMAP_WEB_API_KEY);
+
+ // 构建URL
+ String url = HttpUtils.buildWeatherUrl("北京", false);
+ if (url == null) {
+ Log.e(TAG, "URL构建失败");
+ return false;
+ }
+
+ Log.d(TAG, "测试URL: " + url);
+
+ // 发送请求
+ String response = HttpUtils.sendHttpRequest(url);
+ if (response == null || response.isEmpty()) {
+ Log.e(TAG, "API请求失败,无响应");
+ return false;
+ }
+
+ Log.d(TAG, "API返回数据: " + response);
+
+ // 解析响应,检查状态码
+ try {
+ JSONObject jsonObject = new JSONObject(response);
+ String status = jsonObject.getString("status");
+
+ if ("1".equals(status)) {
+ Log.d(TAG, "API请求成功,状态码: " + status);
+
+ // 验证返回的live数据
+ if (jsonObject.has("lives")) {
+ JSONArray lives = jsonObject.getJSONArray("lives");
+ if (lives.length() > 0) {
+ JSONObject liveData = lives.getJSONObject(0);
+ String city = liveData.getString("city");
+ String weather = liveData.getString("weather");
+
+ Log.d(TAG, "获取到城市: " + city + ", 天气: " + weather);
+ return true;
+ } else {
+ Log.e(TAG, "lives数组为空");
+ }
+ } else {
+ Log.e(TAG, "返回数据中不包含lives字段");
+ }
+ } else {
+ Log.e(TAG, "API请求返回错误状态码: " + status);
+ if (jsonObject.has("info")) {
+ Log.e(TAG, "错误信息: " + jsonObject.getString("info"));
+ }
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "解析JSON数据失败: " + e.getMessage(), e);
+ return false;
+ }
+
+ return false;
+ } catch (Exception e) {
+ Log.e(TAG, "API测试异常: " + e.getMessage(), e);
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/BackgroundManager.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/BackgroundManager.java
new file mode 100644
index 0000000..cf2ba1e
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/BackgroundManager.java
@@ -0,0 +1,212 @@
+package com.example.myapplication.utils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 背景图片管理工具类
+ * 用于保存和读取用户自定义的背景图片
+ */
+public class BackgroundManager {
+ private static final String TAG = "BackgroundManager";
+
+ // SharedPreferences 键值
+ private static final String PREFS_NAME = "BackgroundSettings";
+ private static final String KEY_BG_PREFIX = "background_weather_";
+
+ // 支持的天气类型
+ public static final String WEATHER_TYPE_SUNNY = "晴";
+ public static final String WEATHER_TYPE_CLOUDY = "阴";
+ public static final String WEATHER_TYPE_RAINY = "雨";
+ public static final String WEATHER_TYPE_SNOWY = "雪";
+ public static final String WEATHER_TYPE_FOGGY = "雾";
+ public static final String WEATHER_TYPE_DEFAULT = "default";
+
+ /**
+ * 保存背景图片
+ * @param context 上下文
+ * @param weatherType 天气类型(晴、阴、雨等)
+ * @param imageUri 图片Uri
+ * @return 是否保存成功
+ */
+ public static boolean saveBackgroundImage(Context context, String weatherType, Uri imageUri) {
+ try {
+ // 确保天气类型不为空
+ if (weatherType == null || weatherType.isEmpty()) {
+ Log.e(TAG, "天气类型不能为空");
+ return false;
+ }
+
+ // 获取标准化的天气类型
+ String normalizedWeatherType = normalizeWeatherType(weatherType);
+
+ // 获取输入流
+ InputStream inputStream = context.getContentResolver().openInputStream(imageUri);
+ if (inputStream == null) {
+ Log.e(TAG, "无法打开图片流");
+ return false;
+ }
+
+ // 读取图片并压缩
+ Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
+ inputStream.close();
+
+ // 压缩图片避免内存问题
+ if (bitmap.getWidth() > 1920 || bitmap.getHeight() > 1080) {
+ float ratio = Math.min(1920f / bitmap.getWidth(), 1080f / bitmap.getHeight());
+ int width = Math.round(bitmap.getWidth() * ratio);
+ int height = Math.round(bitmap.getHeight() * ratio);
+ bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
+ }
+
+ // 保存到内部存储
+ String filename = getFilename(normalizedWeatherType);
+ FileOutputStream outputStream = context.openFileOutput(filename, Context.MODE_PRIVATE);
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
+ outputStream.close();
+
+ // 记录使用自定义背景
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(KEY_BG_PREFIX + normalizedWeatherType, true);
+ editor.apply();
+
+ Log.d(TAG, "背景图片已保存: " + normalizedWeatherType);
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "保存背景图片失败: " + e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 获取背景图片
+ * @param context 上下文
+ * @param weatherType 天气类型(晴、阴、雨等)
+ * @return 背景图片位图,如果不存在则返回null
+ */
+ public static Bitmap getBackgroundImage(Context context, String weatherType) {
+ try {
+ // 获取标准化的天气类型
+ String normalizedWeatherType = normalizeWeatherType(weatherType);
+
+ // 检查是否有自定义背景
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ boolean hasCustomBg = prefs.getBoolean(KEY_BG_PREFIX + normalizedWeatherType, false);
+
+ if (!hasCustomBg) {
+ Log.d(TAG, "该天气类型没有自定义背景: " + normalizedWeatherType);
+ return null;
+ }
+
+ // 从文件读取图片
+ String filename = getFilename(normalizedWeatherType);
+ File file = new File(context.getFilesDir(), filename);
+
+ if (!file.exists()) {
+ Log.e(TAG, "背景图片文件不存在: " + filename);
+ return null;
+ }
+
+ FileInputStream inputStream = new FileInputStream(file);
+ Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
+ inputStream.close();
+
+ return bitmap;
+ } catch (IOException e) {
+ Log.e(TAG, "获取背景图片失败: " + e.getMessage(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 重置背景图片为默认
+ * @param context 上下文
+ * @param weatherType 天气类型(晴、阴、雨等)
+ * @return 是否成功删除
+ */
+ public static boolean resetBackground(Context context, String weatherType) {
+ try {
+ // 获取标准化的天气类型
+ String normalizedWeatherType = normalizeWeatherType(weatherType);
+
+ // 删除记录
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.remove(KEY_BG_PREFIX + normalizedWeatherType);
+ editor.apply();
+
+ // 删除文件
+ String filename = getFilename(normalizedWeatherType);
+ File file = new File(context.getFilesDir(), filename);
+ if (file.exists()) {
+ boolean deleted = file.delete();
+ Log.d(TAG, "背景图片已删除: " + deleted);
+ return deleted;
+ }
+
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "重置背景图片失败: " + e.getMessage(), e);
+ return false;
+ }
+ }
+
+ /**
+ * 检查是否有自定义背景
+ * @param context 上下文
+ * @param weatherType 天气类型(晴、阴、雨等)
+ * @return 是否有自定义背景
+ */
+ public static boolean hasCustomBackground(Context context, String weatherType) {
+ // 获取标准化的天气类型
+ String normalizedWeatherType = normalizeWeatherType(weatherType);
+
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ return prefs.getBoolean(KEY_BG_PREFIX + normalizedWeatherType, false);
+ }
+
+ /**
+ * 获取文件名
+ * @param weatherType 天气类型
+ * @return 文件名
+ */
+ private static String getFilename(String weatherType) {
+ return "bg_weather_" + weatherType.replace(" ", "_").toLowerCase() + ".jpg";
+ }
+
+ /**
+ * 标准化天气类型
+ * @param weatherType 原始天气类型
+ * @return 标准化的天气类型
+ */
+ private static String normalizeWeatherType(String weatherType) {
+ if (weatherType == null || weatherType.isEmpty()) {
+ return WEATHER_TYPE_DEFAULT;
+ }
+
+ if (weatherType.contains(WEATHER_TYPE_SUNNY)) {
+ return WEATHER_TYPE_SUNNY;
+ } else if (weatherType.contains(WEATHER_TYPE_CLOUDY)) {
+ return WEATHER_TYPE_CLOUDY;
+ } else if (weatherType.contains(WEATHER_TYPE_RAINY)) {
+ return WEATHER_TYPE_RAINY;
+ } else if (weatherType.contains(WEATHER_TYPE_SNOWY)) {
+ return WEATHER_TYPE_SNOWY;
+ } else if (weatherType.contains(WEATHER_TYPE_FOGGY)) {
+ return WEATHER_TYPE_FOGGY;
+ } else {
+ return WEATHER_TYPE_DEFAULT;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/CityManager.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/CityManager.java
new file mode 100644
index 0000000..9ccd9d6
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/CityManager.java
@@ -0,0 +1,236 @@
+package com.example.myapplication.utils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.example.myapplication.model.City;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 城市管理工具类
+ * 用于保存、获取和管理城市列表
+ */
+public class CityManager {
+ private static final String TAG = "CityManager";
+ private static final String PREFS_NAME = "CityPreferences";
+ private static final String KEY_CITIES = "cities";
+ private static final String KEY_CURRENT_CITY = "current_city";
+
+ /**
+ * 获取所有保存的城市
+ * @param context 上下文
+ * @return 城市列表
+ */
+ public static List getAllCities(Context context) {
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ String citiesJson = prefs.getString(KEY_CITIES, "[]");
+ List cities = new ArrayList<>();
+
+ try {
+ JSONArray jsonArray = new JSONArray(citiesJson);
+ for (int i = 0; i < jsonArray.length(); i++) {
+ JSONObject cityObj = jsonArray.getJSONObject(i);
+ City city = new City();
+ city.setId(cityObj.getInt("id"));
+ city.setName(cityObj.getString("name"));
+ city.setSelected(cityObj.getBoolean("isSelected"));
+ cities.add(city);
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "解析城市列表失败: " + e.getMessage(), e);
+ }
+
+ // 我们不再自动添加默认城市,允许返回空列表
+ Log.d(TAG, "获取到 " + cities.size() + " 个收藏城市");
+ return cities;
+ }
+
+ /**
+ * 保存城市列表
+ * @param context 上下文
+ * @param cities 城市列表
+ */
+ public static void saveCities(Context context, List cities) {
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ JSONArray jsonArray = new JSONArray();
+
+ try {
+ for (City city : cities) {
+ JSONObject cityObj = new JSONObject();
+ cityObj.put("id", city.getId());
+ cityObj.put("name", city.getName());
+ cityObj.put("isSelected", city.isSelected());
+ jsonArray.put(cityObj);
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "序列化城市列表失败: " + e.getMessage(), e);
+ }
+
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString(KEY_CITIES, jsonArray.toString());
+ editor.apply();
+ }
+
+ /**
+ * 添加新城市
+ * @param context 上下文
+ * @param cityName 城市名称
+ * @param setAsSelected 是否设为当前选中
+ * @return 是否添加成功
+ */
+ public static boolean addCity(Context context, String cityName, boolean setAsSelected) {
+ List cities = getAllCities(context);
+
+ // 检查是否已存在该城市
+ for (City city : cities) {
+ if (city.getName().equals(cityName)) {
+ if (setAsSelected) {
+ setCurrentCity(context, cityName);
+ }
+ return false; // 城市已存在
+ }
+ }
+
+ // 生成新的ID
+ int newId = 1;
+ if (!cities.isEmpty()) {
+ newId = cities.get(cities.size() - 1).getId() + 1;
+ }
+
+ // 如果设为当前选中,先将其他城市设为未选中
+ if (setAsSelected) {
+ for (City city : cities) {
+ city.setSelected(false);
+ }
+ }
+
+ // 添加新城市
+ City newCity = new City(newId, cityName, setAsSelected);
+ cities.add(newCity);
+
+ // 保存城市列表
+ saveCities(context, cities);
+
+ // 如果设为当前选中,同时更新当前城市
+ if (setAsSelected) {
+ setCurrentCity(context, cityName);
+ }
+
+ return true;
+ }
+
+ /**
+ * 删除城市
+ * @param context 上下文
+ * @param cityName 城市名称
+ * @return 是否删除成功
+ */
+ public static boolean deleteCity(Context context, String cityName) {
+ List cities = getAllCities(context);
+ boolean removed = false;
+ boolean wasSelected = false;
+
+ // 找到并删除城市
+ for (int i = 0; i < cities.size(); i++) {
+ if (cities.get(i).getName().equals(cityName)) {
+ wasSelected = cities.get(i).isSelected();
+ cities.remove(i);
+ removed = true;
+ break;
+ }
+ }
+
+ if (removed) {
+ // 如果删除的是当前选中城市,则选中列表中的第一个城市
+ if (wasSelected && !cities.isEmpty()) {
+ cities.get(0).setSelected(true);
+ setCurrentCity(context, cities.get(0).getName());
+ }
+
+ // 保存城市列表
+ saveCities(context, cities);
+ }
+
+ return removed;
+ }
+
+ /**
+ * 设置当前城市
+ * @param context 上下文
+ * @param cityName 城市名称
+ * @return 是否设置成功
+ */
+ public static boolean setCurrentCity(Context context, String cityName) {
+ List cities = getAllCities(context);
+ boolean found = false;
+
+ // 更新选中状态
+ for (City city : cities) {
+ boolean isMatch = city.getName().equals(cityName);
+ city.setSelected(isMatch);
+ if (isMatch) {
+ found = true;
+ }
+ }
+
+ if (found) {
+ // 保存城市列表
+ saveCities(context, cities);
+
+ // 保存当前城市名称
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString(KEY_CURRENT_CITY, cityName);
+ editor.apply();
+ }
+
+ return found;
+ }
+
+ /**
+ * 获取当前选中的城市
+ * @param context 上下文
+ * @return 当前选中的城市
+ */
+ public static City getCurrentCity(Context context) {
+ List cities = getAllCities(context);
+
+ // 首先通过选中状态查找
+ for (City city : cities) {
+ if (city.isSelected()) {
+ return city;
+ }
+ }
+
+ // 如果没有选中状态的城市,从SharedPreferences获取
+ SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ String currentCityName = prefs.getString(KEY_CURRENT_CITY, "");
+
+ if (!currentCityName.isEmpty()) {
+ for (City city : cities) {
+ if (city.getName().equals(currentCityName)) {
+ city.setSelected(true);
+ return city;
+ }
+ }
+ }
+
+ // 如果还是没有找到,返回列表中的第一个城市
+ if (!cities.isEmpty()) {
+ cities.get(0).setSelected(true);
+ setCurrentCity(context, cities.get(0).getName());
+ return cities.get(0);
+ } else {
+ // 如果没有收藏的城市,返回一个临时的City对象(不保存到收藏列表)
+ Log.d(TAG, "没有收藏的城市,返回一个临时城市对象");
+ return new City(0, "", false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/HttpUtils.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/HttpUtils.java
new file mode 100644
index 0000000..aa81e23
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/HttpUtils.java
@@ -0,0 +1,273 @@
+package com.example.myapplication.utils;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.URLEncoder;
+
+/**
+ * 网络请求工具类
+ */
+public class HttpUtils {
+ private static final String TAG = "HttpUtils";
+ private static final int CONNECTION_TIMEOUT = 20000; // 20秒
+ private static final int READ_TIMEOUT = 20000; // 20秒
+
+ // 备用API地址
+ private static final String BACKUP_API = "https://api.map.baidu.com/weather/v1/"; // 示例,需要替换为真实的备用API
+
+ /**
+ * 检查网络连接状态
+ * @param context 上下文
+ * @return 是否连接到网络
+ */
+ public static boolean isNetworkAvailable(Context context) {
+ ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (connectivityManager != null) {
+ NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
+ boolean isConnected = networkInfo != null && networkInfo.isConnected();
+ Log.d(TAG, "网络连接状态: " + (isConnected ? "已连接" : "未连接"));
+ return isConnected;
+ }
+ Log.d(TAG, "无法获取连接管理器,网络未连接");
+ return false;
+ }
+
+ /**
+ * 发送GET请求获取数据
+ * @param urlString URL地址
+ * @return 返回的字符串数据
+ */
+ public static String sendHttpRequest(String urlString) {
+ HttpURLConnection connection = null;
+ BufferedReader reader = null;
+ StringBuilder response = new StringBuilder();
+
+ try {
+ Log.d(TAG, "开始HTTP请求: " + urlString);
+
+ // 尝试DNS预解析
+ try {
+ String host = new URL(urlString).getHost();
+ Log.d(TAG, "尝试DNS预解析: " + host);
+ InetAddress address = InetAddress.getByName(host);
+ Log.d(TAG, "DNS解析成功: " + host + " -> " + address.getHostAddress());
+ } catch (Exception e) {
+ Log.e(TAG, "DNS预解析失败: " + e.getMessage());
+ }
+
+ URL url = new URL(urlString);
+ connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("GET");
+ connection.setConnectTimeout(CONNECTION_TIMEOUT);
+ connection.setReadTimeout(READ_TIMEOUT);
+ connection.setRequestProperty("Accept", "application/json");
+ connection.setRequestProperty("User-Agent", "MinimalistWeather-App");
+
+ // 获取响应代码
+ int responseCode = connection.getResponseCode();
+ Log.d(TAG, "HTTP响应代码: " + responseCode);
+
+ if (responseCode == HttpURLConnection.HTTP_OK) {
+ InputStream inputStream = connection.getInputStream();
+ reader = new BufferedReader(new InputStreamReader(inputStream));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ response.append(line);
+ }
+
+ String responseData = response.toString();
+ Log.d(TAG, "HTTP响应数据: " + (responseData.length() > 200 ?
+ responseData.substring(0, 200) + "..." : responseData));
+
+ return responseData;
+ } else {
+ Log.e(TAG, "HTTP请求失败,错误代码: " + responseCode);
+
+ // 读取错误流
+ InputStream errorStream = connection.getErrorStream();
+ if (errorStream != null) {
+ reader = new BufferedReader(new InputStreamReader(errorStream));
+ String line;
+ StringBuilder errorResponse = new StringBuilder();
+ while ((line = reader.readLine()) != null) {
+ errorResponse.append(line);
+ }
+ Log.e(TAG, "错误响应: " + errorResponse.toString());
+ }
+
+ return null;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "HTTP请求异常: " + e.getMessage(), e);
+ e.printStackTrace();
+ return null;
+ } finally {
+ Log.d(TAG, "HTTP请求结束,清理资源");
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ }
+
+ /**
+ * 构建高德地图天气查询URL
+ * @param cityName 城市名称
+ * @param isForcast 是否获取天气预报(true获取预报,false获取实时天气)
+ * @return 构建好的URL字符串
+ */
+ public static String buildWeatherUrl(String cityName, boolean isForcast) {
+ StringBuilder urlBuilder = new StringBuilder(WeatherApiConstants.WEATHER_BASE_URL);
+
+ try {
+ // 获取城市编码
+ String cityCode = Util.getCityCode(cityName);
+ Log.d(TAG, "城市: " + cityName + ", 城市编码: " + cityCode);
+
+ urlBuilder.append("?").append(WeatherApiConstants.PARAM_KEY).append("=")
+ .append(WeatherApiConstants.AMAP_WEB_API_KEY);
+ urlBuilder.append("&").append(WeatherApiConstants.PARAM_CITY).append("=")
+ .append(URLEncoder.encode(cityCode, "UTF-8"));
+
+ // 决定返回实况天气还是预报天气
+ String extensions = isForcast ?
+ WeatherApiConstants.VALUE_EXTENSIONS_ALL :
+ WeatherApiConstants.VALUE_EXTENSIONS_BASE;
+ urlBuilder.append("&").append(WeatherApiConstants.PARAM_EXTENSIONS).append("=")
+ .append(extensions);
+
+ // 返回JSON格式数据
+ urlBuilder.append("&").append(WeatherApiConstants.PARAM_OUTPUT).append("=")
+ .append(WeatherApiConstants.VALUE_OUTPUT_JSON);
+
+ String finalUrl = urlBuilder.toString();
+ Log.d(TAG, "构建的URL: " + finalUrl);
+ return finalUrl;
+ } catch (Exception e) {
+ Log.e(TAG, "构建URL时出错: " + e.getMessage(), e);
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 使用备用天气API(模拟方法)
+ */
+ public static String useBackupWeatherApi(String cityName) {
+ // 此处应实现备用API的调用逻辑
+ // 由于目前没有实际的备用API,这里只返回模拟数据
+ String jsonTemplate = "{\n" +
+ " \"status\": \"1\",\n" +
+ " \"count\": \"1\",\n" +
+ " \"info\": \"OK\",\n" +
+ " \"infocode\": \"10000\",\n" +
+ " \"lives\": [\n" +
+ " {\n" +
+ " \"province\": \"北京\",\n" +
+ " \"city\": \"" + cityName + "\",\n" +
+ " \"adcode\": \"110000\",\n" +
+ " \"weather\": \"晴\",\n" +
+ " \"temperature\": \"25\",\n" +
+ " \"winddirection\": \"西南\",\n" +
+ " \"windpower\": \"4\",\n" +
+ " \"humidity\": \"30\",\n" +
+ " \"reporttime\": \"2023-07-14 16:00:00\"\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ Log.d(TAG, "使用备用API返回模拟数据");
+ return jsonTemplate;
+ }
+
+ /**
+ * 使用备用天气预报API(模拟方法)
+ */
+ public static String useBackupForecastApi(String cityName) {
+ // 此处应实现备用API的调用逻辑
+ // 由于目前没有实际的备用API,这里只返回模拟数据
+ String jsonTemplate = "{\n" +
+ " \"status\": \"1\",\n" +
+ " \"count\": \"1\",\n" +
+ " \"info\": \"OK\",\n" +
+ " \"infocode\": \"10000\",\n" +
+ " \"forecasts\": [\n" +
+ " {\n" +
+ " \"city\": \"" + cityName + "\",\n" +
+ " \"adcode\": \"110000\",\n" +
+ " \"province\": \"北京\",\n" +
+ " \"reporttime\": \"2023-07-14 16:00:00\",\n" +
+ " \"casts\": [\n" +
+ " {\n" +
+ " \"date\": \"2023-07-14\",\n" +
+ " \"week\": \"5\",\n" +
+ " \"dayweather\": \"晴\",\n" +
+ " \"nightweather\": \"多云\",\n" +
+ " \"daytemp\": \"28\",\n" +
+ " \"nighttemp\": \"18\",\n" +
+ " \"daywind\": \"西南\",\n" +
+ " \"nightwind\": \"西南\",\n" +
+ " \"daypower\": \"4\",\n" +
+ " \"nightpower\": \"3\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"date\": \"2023-07-15\",\n" +
+ " \"week\": \"6\",\n" +
+ " \"dayweather\": \"多云\",\n" +
+ " \"nightweather\": \"小雨\",\n" +
+ " \"daytemp\": \"26\",\n" +
+ " \"nighttemp\": \"17\",\n" +
+ " \"daywind\": \"东南\",\n" +
+ " \"nightwind\": \"东南\",\n" +
+ " \"daypower\": \"3\",\n" +
+ " \"nightpower\": \"3\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"date\": \"2023-07-16\",\n" +
+ " \"week\": \"7\",\n" +
+ " \"dayweather\": \"小雨\",\n" +
+ " \"nightweather\": \"阴\",\n" +
+ " \"daytemp\": \"24\",\n" +
+ " \"nighttemp\": \"16\",\n" +
+ " \"daywind\": \"东北\",\n" +
+ " \"nightwind\": \"东北\",\n" +
+ " \"daypower\": \"3\",\n" +
+ " \"nightpower\": \"2\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"date\": \"2023-07-17\",\n" +
+ " \"week\": \"1\",\n" +
+ " \"dayweather\": \"多云\",\n" +
+ " \"nightweather\": \"晴\",\n" +
+ " \"daytemp\": \"25\",\n" +
+ " \"nighttemp\": \"16\",\n" +
+ " \"daywind\": \"西北\",\n" +
+ " \"nightwind\": \"西北\",\n" +
+ " \"daypower\": \"3\",\n" +
+ " \"nightpower\": \"2\"\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ Log.d(TAG, "使用备用API返回天气预报模拟数据");
+ return jsonTemplate;
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/SettingsManager.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/SettingsManager.java
new file mode 100644
index 0000000..8436453
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/SettingsManager.java
@@ -0,0 +1,168 @@
+package com.example.myapplication.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.core.content.ContextCompat;
+
+import com.example.myapplication.SettingsActivity;
+import com.example.myapplication.R;
+
+/**
+ * 设置管理工具类
+ * 用于管理和应用应用设置
+ */
+public class SettingsManager {
+ private static final String TAG = "SettingsManager";
+
+ /**
+ * 将摄氏度转换为华氏度
+ * @param celsius 摄氏度温度
+ * @return 华氏度温度
+ */
+ public static int celsiusToFahrenheit(int celsius) {
+ return (celsius * 9 / 5) + 32;
+ }
+
+ /**
+ * 根据设置获取格式化的温度字符串
+ * @param context 上下文
+ * @param tempCelsius 摄氏度温度值
+ * @return 根据设置格式化的温度字符串
+ */
+ public static String getFormattedTemperature(Context context, int tempCelsius) {
+ SharedPreferences prefs = context.getSharedPreferences(SettingsActivity.PREFS_NAME, Context.MODE_PRIVATE);
+ String tempUnit = prefs.getString(SettingsActivity.KEY_TEMP_UNIT, SettingsActivity.TEMP_UNIT_CELSIUS);
+
+ if (SettingsActivity.TEMP_UNIT_FAHRENHEIT.equals(tempUnit)) {
+ int tempFahrenheit = celsiusToFahrenheit(tempCelsius);
+ return tempFahrenheit + "°F";
+ } else {
+ return tempCelsius + "°C";
+ }
+ }
+
+ /**
+ * 获取温度范围的格式化字符串
+ * @param context 上下文
+ * @param minTempCelsius 最低温度(摄氏度)
+ * @param maxTempCelsius 最高温度(摄氏度)
+ * @return 格式化的温度范围字符串
+ */
+ public static String getFormattedTemperatureRange(Context context, int minTempCelsius, int maxTempCelsius) {
+ SharedPreferences prefs = context.getSharedPreferences(SettingsActivity.PREFS_NAME, Context.MODE_PRIVATE);
+ String tempUnit = prefs.getString(SettingsActivity.KEY_TEMP_UNIT, SettingsActivity.TEMP_UNIT_CELSIUS);
+
+ if (SettingsActivity.TEMP_UNIT_FAHRENHEIT.equals(tempUnit)) {
+ int minTempF = celsiusToFahrenheit(minTempCelsius);
+ int maxTempF = celsiusToFahrenheit(maxTempCelsius);
+ return minTempF + "~" + maxTempF + "°F";
+ } else {
+ return minTempCelsius + "~" + maxTempCelsius + "°C";
+ }
+ }
+
+ /**
+ * 应用主题设置
+ * @param activity 活动
+ */
+ public static void applyThemeSetting(Activity activity) {
+ boolean isDarkMode = isDarkModeEnabled(activity);
+
+ Log.d(TAG, "应用主题设置: " + (isDarkMode ? "深色模式" : "浅色模式"));
+
+ // 设置应用主题,但不重建Activity
+ int currentNightMode = AppCompatDelegate.getDefaultNightMode();
+ int targetNightMode = isDarkMode ?
+ AppCompatDelegate.MODE_NIGHT_YES :
+ AppCompatDelegate.MODE_NIGHT_NO;
+
+ if (currentNightMode != targetNightMode) {
+ Log.d(TAG, "切换夜间模式: " + currentNightMode + " -> " + targetNightMode);
+ AppCompatDelegate.setDefaultNightMode(targetNightMode);
+
+ // 注意:这里不调用activity的recreate()方法
+ // 让系统自动处理Activity的重建,这样能确保状态正确保存和恢复
+ }
+ }
+
+ /**
+ * 获取更新频率(分钟)
+ * @param context 上下文
+ * @return 更新频率,单位为分钟,固定为30分钟
+ */
+ public static int getUpdateFrequency(Context context) {
+ // 不再从偏好设置中读取,固定返回30分钟
+ return 30;
+ }
+
+ /**
+ * 是否使用华氏度
+ * @param context 上下文
+ * @return 是否使用华氏度
+ */
+ public static boolean isFahrenheitEnabled(Context context) {
+ SharedPreferences prefs = context.getSharedPreferences(SettingsActivity.PREFS_NAME, Context.MODE_PRIVATE);
+ String tempUnit = prefs.getString(SettingsActivity.KEY_TEMP_UNIT, SettingsActivity.TEMP_UNIT_CELSIUS);
+ return SettingsActivity.TEMP_UNIT_FAHRENHEIT.equals(tempUnit);
+ }
+
+ /**
+ * 是否启用暗黑模式
+ * @param context 上下文
+ * @return 是否启用暗黑模式
+ */
+ public static boolean isDarkModeEnabled(Context context) {
+ SharedPreferences prefs = context.getSharedPreferences(SettingsActivity.PREFS_NAME, Context.MODE_PRIVATE);
+ return prefs.getBoolean(SettingsActivity.KEY_THEME_MODE, false);
+ }
+
+ /**
+ * 获取深色模式主题下UI组件的暗色值
+ * @param context 上下文
+ * @param resourceId 颜色资源ID
+ * @return 在深色模式下调整后的颜色
+ */
+ public static int getDarkModeColor(Context context, int resourceId) {
+ if (!isDarkModeEnabled(context)) {
+ return ContextCompat.getColor(context, resourceId);
+ }
+
+ // 根据原始颜色返回更暗的颜色
+ if (resourceId == R.color.colorPrimary) {
+ return ContextCompat.getColor(context, R.color.colorPrimaryDark);
+ } else if (resourceId == R.color.colorAccent) {
+ return ContextCompat.getColor(context, R.color.colorAccent);
+ } else if (resourceId == R.color.alert_red) {
+ return ContextCompat.getColor(context, R.color.alert_red);
+ } else if (resourceId == R.color.alert_orange) {
+ return ContextCompat.getColor(context, R.color.alert_orange);
+ } else if (resourceId == R.color.alert_yellow) {
+ return ContextCompat.getColor(context, R.color.alert_yellow);
+ } else if (resourceId == R.color.alert_blue) {
+ return ContextCompat.getColor(context, R.color.alert_blue);
+ } else if (resourceId == R.color.alert_default) {
+ return ContextCompat.getColor(context, R.color.alert_default);
+ } else {
+ // 没有特定映射的颜色,返回原始颜色或降低亮度
+ int originalColor = ContextCompat.getColor(context, resourceId);
+ return darkenColor(originalColor, 0.7f); // 降低亮度到70%
+ }
+ }
+
+ /**
+ * 降低颜色亮度
+ * @param color 原始颜色
+ * @param factor 暗化因子(0-1),越小越暗
+ * @return 降低亮度后的颜色
+ */
+ private static int darkenColor(int color, float factor) {
+ float[] hsv = new float[3];
+ android.graphics.Color.colorToHSV(color, hsv);
+ hsv[2] *= factor; // 降低亮度
+ return android.graphics.Color.HSVToColor(android.graphics.Color.alpha(color), hsv);
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/Util.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/Util.java
new file mode 100644
index 0000000..a90e3fc
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/Util.java
@@ -0,0 +1,247 @@
+package com.example.myapplication.utils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import com.example.myapplication.R;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 工具类,实现城市名称与编码的转换以及天气图片的获取
+ */
+public class Util {
+
+ // 城市名称到城市编码的映射表
+ private static final Map CITY_MAP = new HashMap<>();
+
+ // 天气状况到图片资源ID的映射表 - 白天
+ private static final Map WEATHER_IMAGE_DAY_MAP = new HashMap<>();
+
+ // 天气状况到图片资源ID的映射表 - 夜晚
+ private static final Map WEATHER_IMAGE_NIGHT_MAP = new HashMap<>();
+
+ // 天气状态到背景资源ID的映射表
+ private static final Map WEATHER_BACKGROUND_MAP = new HashMap<>();
+
+ // 静态初始化块,初始化城市映射表
+ static {
+ // 初始化部分常用城市编码
+ CITY_MAP.put("北京", "110000");
+ CITY_MAP.put("上海", "310000");
+ CITY_MAP.put("广州", "440100");
+ CITY_MAP.put("深圳", "440300");
+ CITY_MAP.put("杭州", "330100");
+ CITY_MAP.put("南京", "320100");
+ CITY_MAP.put("天津", "120000");
+ CITY_MAP.put("重庆", "500000");
+ CITY_MAP.put("武汉", "420100");
+ CITY_MAP.put("成都", "510100");
+ CITY_MAP.put("西安", "610100");
+ CITY_MAP.put("长沙", "430100");
+ CITY_MAP.put("郑州", "410100");
+ CITY_MAP.put("青岛", "370200");
+ CITY_MAP.put("济南", "370100");
+ CITY_MAP.put("大连", "210200");
+ CITY_MAP.put("沈阳", "210100");
+ CITY_MAP.put("哈尔滨", "230100");
+ CITY_MAP.put("长春", "220100");
+ CITY_MAP.put("厦门", "350200");
+ CITY_MAP.put("福州", "350100");
+ CITY_MAP.put("南宁", "450100");
+ CITY_MAP.put("海口", "460100");
+ CITY_MAP.put("昆明", "530100");
+ CITY_MAP.put("贵阳", "520100");
+ CITY_MAP.put("南昌", "360100");
+ CITY_MAP.put("合肥", "340100");
+ CITY_MAP.put("兰州", "620100");
+ CITY_MAP.put("太原", "140100");
+ CITY_MAP.put("石家庄", "130100");
+
+ // 初始化白天天气图片资源映射
+ WEATHER_IMAGE_DAY_MAP.put("晴", R.drawable.sunny);
+ WEATHER_IMAGE_DAY_MAP.put("多云", R.drawable.cloudy);
+ WEATHER_IMAGE_DAY_MAP.put("阴", R.drawable.overcast);
+ WEATHER_IMAGE_DAY_MAP.put("小雨", R.drawable.light_rain);
+ WEATHER_IMAGE_DAY_MAP.put("中雨", R.drawable.moderate_rain);
+ WEATHER_IMAGE_DAY_MAP.put("大雨", R.drawable.heavy_rain);
+ WEATHER_IMAGE_DAY_MAP.put("暴雨", R.drawable.storm);
+ WEATHER_IMAGE_DAY_MAP.put("雷阵雨", R.drawable.storm);
+ WEATHER_IMAGE_DAY_MAP.put("雨夹雪", R.drawable.light_rain);
+ WEATHER_IMAGE_DAY_MAP.put("阵雨", R.drawable.light_rain);
+ WEATHER_IMAGE_DAY_MAP.put("雾", R.drawable.overcast);
+ WEATHER_IMAGE_DAY_MAP.put("霾", R.drawable.overcast);
+
+ // 初始化夜间天气图片资源映射
+ WEATHER_IMAGE_NIGHT_MAP.put("晴", R.drawable.sunny); // 使用同样的图标,后续可以替换为夜间专用图标
+ WEATHER_IMAGE_NIGHT_MAP.put("多云", R.drawable.cloudy);
+ WEATHER_IMAGE_NIGHT_MAP.put("阴", R.drawable.overcast);
+ WEATHER_IMAGE_NIGHT_MAP.put("小雨", R.drawable.light_rain);
+ WEATHER_IMAGE_NIGHT_MAP.put("中雨", R.drawable.moderate_rain);
+ WEATHER_IMAGE_NIGHT_MAP.put("大雨", R.drawable.heavy_rain);
+ WEATHER_IMAGE_NIGHT_MAP.put("暴雨", R.drawable.storm);
+ WEATHER_IMAGE_NIGHT_MAP.put("雷阵雨", R.drawable.storm);
+ WEATHER_IMAGE_NIGHT_MAP.put("雨夹雪", R.drawable.light_rain);
+ WEATHER_IMAGE_NIGHT_MAP.put("阵雨", R.drawable.light_rain);
+ WEATHER_IMAGE_NIGHT_MAP.put("雾", R.drawable.overcast);
+ WEATHER_IMAGE_NIGHT_MAP.put("霾", R.drawable.overcast);
+
+ // 初始化天气背景映射
+ WEATHER_BACKGROUND_MAP.put("晴", R.drawable.sunny_background);
+ WEATHER_BACKGROUND_MAP.put("多云", R.drawable.cloudy_background);
+ WEATHER_BACKGROUND_MAP.put("阴", R.drawable.cloudy_background);
+ WEATHER_BACKGROUND_MAP.put("小雨", R.drawable.rainy_background);
+ WEATHER_BACKGROUND_MAP.put("中雨", R.drawable.rainy_background);
+ WEATHER_BACKGROUND_MAP.put("大雨", R.drawable.rainy_background);
+ WEATHER_BACKGROUND_MAP.put("暴雨", R.drawable.rainy_background);
+ WEATHER_BACKGROUND_MAP.put("雷阵雨", R.drawable.rainy_background);
+ WEATHER_BACKGROUND_MAP.put("雨夹雪", R.drawable.rainy_background);
+ WEATHER_BACKGROUND_MAP.put("阵雨", R.drawable.rainy_background);
+ WEATHER_BACKGROUND_MAP.put("雾", R.drawable.cloudy_background);
+ WEATHER_BACKGROUND_MAP.put("霾", R.drawable.cloudy_background);
+ }
+
+ /**
+ * 根据城市名称获取城市编码
+ * @param cityName 城市名称
+ * @return 城市编码,如果不存在则返回城市名称本身
+ */
+ public static String getCityCode(String cityName) {
+ if (cityName == null || cityName.isEmpty()) {
+ return "";
+ }
+
+ // 尝试直接从映射表中获取
+ String cityCode = CITY_MAP.get(cityName);
+
+ // 如果找不到对应的城市编码,则返回城市名称本身
+ // 高德地图API支持使用城市名称直接查询
+ return cityCode != null ? cityCode : cityName;
+ }
+
+ /**
+ * 根据城市编码获取城市名称
+ * @param cityCode 城市编码
+ * @return 城市名称,如果不存在则返回编码本身
+ */
+ public static String getCityName(String cityCode) {
+ if (cityCode == null || cityCode.isEmpty()) {
+ return "";
+ }
+
+ // 遍历映射表查找对应的城市名称
+ for (Map.Entry entry : CITY_MAP.entrySet()) {
+ if (entry.getValue().equals(cityCode)) {
+ return entry.getKey();
+ }
+ }
+
+ // 如果找不到对应的城市名称,则返回编码本身
+ return cityCode;
+ }
+
+ /**
+ * 根据天气状况获取对应的图片资源ID
+ * @param weather 天气状况描述
+ * @return 对应的图片资源ID,如果没有对应图片则返回默认图片
+ */
+ public static int getWeatherImage(String weather) {
+ return getWeatherImageByTime(weather);
+ }
+
+ /**
+ * 判断当前是白天还是夜晚
+ * @return true表示白天,false表示夜晚
+ */
+ public static boolean isDaytime() {
+ // 获取当前小时
+ int hour = java.util.Calendar.getInstance().get(java.util.Calendar.HOUR_OF_DAY);
+ // 假设6点到18点为白天
+ return hour >= 6 && hour < 18;
+ }
+
+ /**
+ * 根据天气状况和当前时间获取对应的图片资源ID
+ * @param weather 天气状况描述
+ * @return 对应的图片资源ID
+ */
+ public static int getWeatherImageByTime(String weather) {
+ try {
+ if (weather == null || weather.isEmpty()) {
+ return R.drawable.sunny; // 默认图片
+ }
+
+ boolean isDaytime = isDaytime();
+ Map imageMap = isDaytime ? WEATHER_IMAGE_DAY_MAP : WEATHER_IMAGE_NIGHT_MAP;
+
+ // 尝试精确匹配
+ Integer imageId = imageMap.get(weather);
+ if (imageId != null) {
+ return imageId;
+ }
+
+ // 如果没有精确匹配,尝试模糊匹配
+ for (Map.Entry entry : imageMap.entrySet()) {
+ if (weather.contains(entry.getKey())) {
+ return entry.getValue();
+ }
+ }
+
+ // 如果没有匹配到,返回默认图片
+ return isDaytime ? R.drawable.sunny : R.drawable.sunny;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return R.drawable.sunny;
+ }
+ }
+
+ /**
+ * 获取与天气状况对应的背景资源ID
+ * @param weather 天气状况描述
+ * @return 背景资源ID
+ */
+ public static int getWeatherBackground(String weather) {
+ try {
+ if (weather == null || weather.isEmpty()) {
+ return R.drawable.sunny_background; // 默认背景
+ }
+
+ // 尝试精确匹配
+ Integer backgroundId = WEATHER_BACKGROUND_MAP.get(weather);
+ if (backgroundId != null) {
+ return backgroundId;
+ }
+
+ // 如果没有精确匹配,尝试模糊匹配
+ for (Map.Entry entry : WEATHER_BACKGROUND_MAP.entrySet()) {
+ if (weather.contains(entry.getKey())) {
+ return entry.getValue();
+ }
+ }
+
+ // 如果没有匹配到,返回默认背景
+ return R.drawable.sunny_background;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return R.drawable.sunny_background;
+ }
+ }
+
+ /**
+ * 安全获取资源ID,确保即使资源不存在也不会导致应用崩溃
+ * @param context 上下文
+ * @param resourceName 资源名称
+ * @param defaultResourceId 默认资源ID
+ * @return 资源ID,如果不存在则返回默认资源ID
+ */
+ public static int getResourceIdSafely(Context context, String resourceName, int defaultResourceId) {
+ try {
+ Resources resources = context.getResources();
+ String packageName = context.getPackageName();
+ int resourceId = resources.getIdentifier(resourceName, "drawable", packageName);
+ return resourceId != 0 ? resourceId : defaultResourceId;
+ } catch (Exception e) {
+ return defaultResourceId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/WeatherApiConstants.java b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/WeatherApiConstants.java
new file mode 100644
index 0000000..9e2adbe
--- /dev/null
+++ b/MinimalistWeather/app/src/main/java/com/example/myapplication/utils/WeatherApiConstants.java
@@ -0,0 +1,33 @@
+package com.example.myapplication.utils;
+
+/**
+ * 天气API相关常量
+ */
+public class WeatherApiConstants {
+ // 高德地图Web服务API的Key值 - 使用新的有效密钥
+ public static final String AMAP_WEB_API_KEY = "3d9386dc59dc3b913037b7f67aa5d576";
+
+ // 天气查询API的基础URL
+ public static final String WEATHER_BASE_URL = "https://restapi.amap.com/v3/weather/weatherInfo";
+
+ // 天气预警API的基础URL
+ public static final String WEATHER_ALERT_URL = "https://restapi.amap.com/v3/weather/weatherInfo/alert";
+
+ // 请求参数名
+ public static final String PARAM_KEY = "key"; // API密钥
+ public static final String PARAM_CITY = "city"; // 城市编码
+ public static final String PARAM_EXTENSIONS = "extensions"; // 气象类型
+ public static final String PARAM_OUTPUT = "output"; // 返回格式
+
+ // 请求参数值
+ public static final String VALUE_EXTENSIONS_BASE = "base"; // 返回实况天气
+ public static final String VALUE_EXTENSIONS_ALL = "all"; // 返回预报天气
+ public static final String VALUE_OUTPUT_JSON = "JSON"; // 返回JSON格式
+ public static final String VALUE_OUTPUT_XML = "XML"; // 返回XML格式
+
+ // 预警级别常量
+ public static final String ALERT_LEVEL_RED = "红色"; // 红色预警
+ public static final String ALERT_LEVEL_ORANGE = "橙色"; // 橙色预警
+ public static final String ALERT_LEVEL_YELLOW = "黄色"; // 黄色预警
+ public static final String ALERT_LEVEL_BLUE = "蓝色"; // 蓝色预警
+}
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/china_landscape.xml b/MinimalistWeather/app/src/main/res/drawable/china_landscape.xml
new file mode 100644
index 0000000..1dbfbcc
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/china_landscape.xml
@@ -0,0 +1,131 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/china_landscape_bg.xml b/MinimalistWeather/app/src/main/res/drawable/china_landscape_bg.xml
new file mode 100644
index 0000000..ae9d14d
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/china_landscape_bg.xml
@@ -0,0 +1,87 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/china_landscape_img.xml b/MinimalistWeather/app/src/main/res/drawable/china_landscape_img.xml
new file mode 100644
index 0000000..1dbfbcc
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/china_landscape_img.xml
@@ -0,0 +1,131 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/cloudy.xml b/MinimalistWeather/app/src/main/res/drawable/cloudy.xml
new file mode 100644
index 0000000..1c471c8
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/cloudy.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/cloudy_background.xml b/MinimalistWeather/app/src/main/res/drawable/cloudy_background.xml
new file mode 100644
index 0000000..87bf55a
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/cloudy_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/edit_text_background.xml b/MinimalistWeather/app/src/main/res/drawable/edit_text_background.xml
new file mode 100644
index 0000000..2e9c76a
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/edit_text_background.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/heavy_rain.xml b/MinimalistWeather/app/src/main/res/drawable/heavy_rain.xml
new file mode 100644
index 0000000..6f9b01f
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/heavy_rain.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/ic_alert_blue.xml b/MinimalistWeather/app/src/main/res/drawable/ic_alert_blue.xml
new file mode 100644
index 0000000..d27f3df
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/ic_alert_blue.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/ic_alert_default.xml b/MinimalistWeather/app/src/main/res/drawable/ic_alert_default.xml
new file mode 100644
index 0000000..743c116
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/ic_alert_default.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/ic_alert_orange.xml b/MinimalistWeather/app/src/main/res/drawable/ic_alert_orange.xml
new file mode 100644
index 0000000..744352e
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/ic_alert_orange.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/ic_alert_red.xml b/MinimalistWeather/app/src/main/res/drawable/ic_alert_red.xml
new file mode 100644
index 0000000..320056e
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/ic_alert_red.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/ic_alert_yellow.xml b/MinimalistWeather/app/src/main/res/drawable/ic_alert_yellow.xml
new file mode 100644
index 0000000..1a2cf04
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/ic_alert_yellow.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/ic_launcher_background.xml b/MinimalistWeather/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MinimalistWeather/app/src/main/res/drawable/ic_launcher_foreground.xml b/MinimalistWeather/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/light_rain.xml b/MinimalistWeather/app/src/main/res/drawable/light_rain.xml
new file mode 100644
index 0000000..df4f3bb
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/light_rain.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/moderate_rain.xml b/MinimalistWeather/app/src/main/res/drawable/moderate_rain.xml
new file mode 100644
index 0000000..fe8a09c
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/moderate_rain.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/overcast.xml b/MinimalistWeather/app/src/main/res/drawable/overcast.xml
new file mode 100644
index 0000000..c6db4b4
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/overcast.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/rainy_background.xml b/MinimalistWeather/app/src/main/res/drawable/rainy_background.xml
new file mode 100644
index 0000000..20f3e8a
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/rainy_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/rounded_button_background.xml b/MinimalistWeather/app/src/main/res/drawable/rounded_button_background.xml
new file mode 100644
index 0000000..ebfa557
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/rounded_button_background.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/storm.xml b/MinimalistWeather/app/src/main/res/drawable/storm.xml
new file mode 100644
index 0000000..dda86be
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/storm.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/sunny.xml b/MinimalistWeather/app/src/main/res/drawable/sunny.xml
new file mode 100644
index 0000000..54812df
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/sunny.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/sunny_background.xml b/MinimalistWeather/app/src/main/res/drawable/sunny_background.xml
new file mode 100644
index 0000000..32e286c
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/sunny_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/sunny_china_landscape.xml b/MinimalistWeather/app/src/main/res/drawable/sunny_china_landscape.xml
new file mode 100644
index 0000000..2421387
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/sunny_china_landscape.xml
@@ -0,0 +1,17 @@
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/timg.xml b/MinimalistWeather/app/src/main/res/drawable/timg.xml
new file mode 100644
index 0000000..30b4cb6
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/timg.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/vertopal_com_china_landscape_img.xml b/MinimalistWeather/app/src/main/res/drawable/vertopal_com_china_landscape_img.xml
new file mode 100644
index 0000000..9efc458
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/vertopal_com_china_landscape_img.xml
@@ -0,0 +1,1237 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MinimalistWeather/app/src/main/res/drawable/vertopal_landscape_overlay.xml b/MinimalistWeather/app/src/main/res/drawable/vertopal_landscape_overlay.xml
new file mode 100644
index 0000000..9729ecf
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/vertopal_landscape_overlay.xml
@@ -0,0 +1,17 @@
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/drawable/yu.xml b/MinimalistWeather/app/src/main/res/drawable/yu.xml
new file mode 100644
index 0000000..6d3f8a4
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/drawable/yu.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/layout/activity_city_manager.xml b/MinimalistWeather/app/src/main/res/layout/activity_city_manager.xml
new file mode 100644
index 0000000..a72f59d
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/layout/activity_city_manager.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/layout/activity_okhttp.xml b/MinimalistWeather/app/src/main/res/layout/activity_okhttp.xml
new file mode 100644
index 0000000..11fe93c
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/layout/activity_okhttp.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/layout/activity_settings.xml b/MinimalistWeather/app/src/main/res/layout/activity_settings.xml
new file mode 100644
index 0000000..7017023
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/layout/activity_settings.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/layout/activity_weather.xml b/MinimalistWeather/app/src/main/res/layout/activity_weather.xml
new file mode 100644
index 0000000..558b2c7
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/layout/activity_weather.xml
@@ -0,0 +1,327 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/layout/item_alert.xml b/MinimalistWeather/app/src/main/res/layout/item_alert.xml
new file mode 100644
index 0000000..99b01c0
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/layout/item_alert.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/layout/item_city.xml b/MinimalistWeather/app/src/main/res/layout/item_city.xml
new file mode 100644
index 0000000..444424d
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/layout/item_city.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/layout/spinner_dropdown_item.xml b/MinimalistWeather/app/src/main/res/layout/spinner_dropdown_item.xml
new file mode 100644
index 0000000..3c38f85
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/layout/spinner_dropdown_item.xml
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/layout/spinner_item.xml b/MinimalistWeather/app/src/main/res/layout/spinner_item.xml
new file mode 100644
index 0000000..5382788
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/layout/spinner_item.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/menu/menu_main.xml b/MinimalistWeather/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..9af9715
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,46 @@
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/MinimalistWeather/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/MinimalistWeather/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/MinimalistWeather/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/MinimalistWeather/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/MinimalistWeather/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/MinimalistWeather/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/MinimalistWeather/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/MinimalistWeather/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/MinimalistWeather/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/MinimalistWeather/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/MinimalistWeather/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/MinimalistWeather/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/MinimalistWeather/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/MinimalistWeather/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/MinimalistWeather/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/MinimalistWeather/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/MinimalistWeather/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/MinimalistWeather/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/MinimalistWeather/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/MinimalistWeather/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/MinimalistWeather/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/MinimalistWeather/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/MinimalistWeather/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/MinimalistWeather/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/MinimalistWeather/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/MinimalistWeather/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/MinimalistWeather/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/MinimalistWeather/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/MinimalistWeather/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/MinimalistWeather/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/MinimalistWeather/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/MinimalistWeather/app/src/main/res/values-night/colors.xml b/MinimalistWeather/app/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000..b6926b9
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,20 @@
+
+
+
+ #1A4559
+ #0A1B24
+ #B32D59
+
+ #080808
+ #BBBBBB
+ #888888
+ #000000
+ #BBBBBB
+
+
+ #B82E2A
+ #B36800
+ #AA9500
+ #1667A8
+ #666666
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/values-night/themes.xml b/MinimalistWeather/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..97d1c79
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/values/colors.xml b/MinimalistWeather/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..db3eaee
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/values/colors.xml
@@ -0,0 +1,30 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+ #3388AA
+ #1F6F91
+ #FF4081
+ #FFFFFF
+ #333333
+ #757575
+ #3388AA
+ #121212
+ #E1E1E1
+ #AAAAAA
+ #000000
+ #E1E1E1
+ #333333
+
+
+ #FFE53935
+ #FFFF9800
+ #FFFFEB3B
+ #FF2196F3
+ #FF9E9E9E
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/values/strings.xml b/MinimalistWeather/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..28a64ef
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/values/strings.xml
@@ -0,0 +1,26 @@
+
+ 极简天气
+ 分享
+ 分享到微信
+ 分享天气信息
+ 分享失败,请稍后重试
+ 暂无天气信息可分享
+ 来自「极简天气」APP
+
+
+ 城市收藏
+ 添加到收藏
+ %s 已在收藏列表中
+ 已将 %s 添加到收藏列表
+ 添加到城市列表
+ 是否将 %s 添加到收藏列表?
+ 取消收藏
+ 是否要取消收藏 %s ?
+ 已取消收藏: %s
+ 至少保留一个城市
+ 收藏城市
+ 已将 %s 设为当前城市
+ 已删除城市: %s
+ 是
+ 否
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/values/themes.xml b/MinimalistWeather/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..e479ff4
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/values/themes.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/xml/backup_rules.xml b/MinimalistWeather/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..4df9255
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/xml/data_extraction_rules.xml b/MinimalistWeather/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/main/res/xml/network_security_config.xml b/MinimalistWeather/app/src/main/res/xml/network_security_config.xml
new file mode 100644
index 0000000..91e3081
--- /dev/null
+++ b/MinimalistWeather/app/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+ restapi.amap.com
+ amap.com
+
+
\ No newline at end of file
diff --git a/MinimalistWeather/app/src/test/java/com/example/myapplication/ExampleUnitTest.kt b/MinimalistWeather/app/src/test/java/com/example/myapplication/ExampleUnitTest.kt
new file mode 100644
index 0000000..e500fb8
--- /dev/null
+++ b/MinimalistWeather/app/src/test/java/com/example/myapplication/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.example.myapplication
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/MinimalistWeather/build.gradle.kts b/MinimalistWeather/build.gradle.kts
new file mode 100644
index 0000000..952b930
--- /dev/null
+++ b/MinimalistWeather/build.gradle.kts
@@ -0,0 +1,6 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.kotlin.android) apply false
+ alias(libs.plugins.kotlin.compose) apply false
+}
\ No newline at end of file
diff --git a/MinimalistWeather/gradle.properties b/MinimalistWeather/gradle.properties
new file mode 100644
index 0000000..20e2a01
--- /dev/null
+++ b/MinimalistWeather/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/MinimalistWeather/gradle/libs.versions.toml b/MinimalistWeather/gradle/libs.versions.toml
new file mode 100644
index 0000000..916e792
--- /dev/null
+++ b/MinimalistWeather/gradle/libs.versions.toml
@@ -0,0 +1,32 @@
+[versions]
+agp = "8.9.1"
+kotlin = "2.0.21"
+coreKtx = "1.16.0"
+junit = "4.13.2"
+junitVersion = "1.2.1"
+espressoCore = "3.6.1"
+lifecycleRuntimeKtx = "2.8.7"
+activityCompose = "1.10.1"
+composeBom = "2024.09.00"
+
+[libraries]
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
+androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
+androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
+androidx-ui = { group = "androidx.compose.ui", name = "ui" }
+androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
+androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
+androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
+androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
+androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
+androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+
diff --git a/MinimalistWeather/gradle/wrapper/gradle-wrapper.jar b/MinimalistWeather/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/MinimalistWeather/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/MinimalistWeather/gradle/wrapper/gradle-wrapper.properties b/MinimalistWeather/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..92e7259
--- /dev/null
+++ b/MinimalistWeather/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 23 23:52:42 CST 2025
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/MinimalistWeather/gradlew b/MinimalistWeather/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/MinimalistWeather/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/MinimalistWeather/gradlew.bat b/MinimalistWeather/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/MinimalistWeather/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/MinimalistWeather/settings.gradle.kts b/MinimalistWeather/settings.gradle.kts
new file mode 100644
index 0000000..7e67774
--- /dev/null
+++ b/MinimalistWeather/settings.gradle.kts
@@ -0,0 +1,24 @@
+pluginManagement {
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "My Application"
+include(":app")
+
\ No newline at end of file
diff --git a/README.md b/README.md
deleted file mode 100644
index fdc9657..0000000
--- a/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# 1MW
-