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 @@ + + + + + + + + + + + +