增加天气显示
This commit is contained in:
44
app/CMakeLists.txt
Normal file
44
app/CMakeLists.txt
Normal file
@@ -0,0 +1,44 @@
|
||||
# For more information about using CMake with Android Studio, read the
|
||||
# documentation: https://d.android.com/studio/projects/add-native-code.html
|
||||
|
||||
# Sets the minimum version of CMake required to build the native library.
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
# Creates and names a library, sets it as either STATIC
|
||||
# or SHARED, and provides the relative paths to its source code.
|
||||
# You can define multiple libraries, and CMake builds them for you.
|
||||
# Gradle automatically packages shared libraries with your APK.
|
||||
|
||||
add_library( # Sets the name of the library.
|
||||
ttstd
|
||||
|
||||
# Sets the library as a shared library.
|
||||
SHARED
|
||||
|
||||
# Provides a relative path to your source file(s).
|
||||
src/main/jni/ttstd.cpp)
|
||||
|
||||
# Searches for a specified prebuilt library and stores the path as a
|
||||
# variable. Because CMake includes system libraries in the search path by
|
||||
# default, you only need to specify the name of the public NDK library
|
||||
# you want to add. CMake verifies that the library exists before
|
||||
# completing its build.
|
||||
|
||||
find_library( # Sets the name of the path variable.
|
||||
log-lib
|
||||
|
||||
# Specifies the name of the NDK library that
|
||||
# you want CMake to locate.
|
||||
log)
|
||||
|
||||
# Specifies libraries CMake should link to your target library. You
|
||||
# can link multiple libraries, such as libraries you define in this
|
||||
# build script, prebuilt third-party libraries, or system libraries.
|
||||
|
||||
target_link_libraries( # Specifies the target library.
|
||||
ttstd
|
||||
|
||||
# Links the target library to the log library
|
||||
# included in the NDK.
|
||||
${log-lib})
|
||||
@@ -63,9 +63,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
buildConfigField "String", "QweatherId", "\"${qweatherConfigs.credential.id}\""
|
||||
buildConfigField "String", "QweatherKey", "\"${qweatherConfigs.credential.key}\""
|
||||
|
||||
manifestPlaceholders = [
|
||||
JPUSH_PKGNAME : applicationId,
|
||||
JPUSH_APPKEY : "d779178d9900d4fb5d633678", //JPush 上注册的包名对应的 Appkey.
|
||||
@@ -84,6 +81,12 @@ android {
|
||||
]
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path file('CMakeLists.txt')
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
keypub {
|
||||
storeFile file(rootProject.ext.signingConfigs.keypub.storeFile)
|
||||
@@ -177,7 +180,8 @@ ext {
|
||||
dependencies {
|
||||
// implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
//Android 4.4+
|
||||
implementation files('libs/QWeather_Public_Android_V4.20.jar')
|
||||
implementation files('libs/QWeather_Public_Android_V5.1.2.jar')
|
||||
implementation 'net.i2p.crypto:eddsa:0.3.0'
|
||||
|
||||
implementation project(path: ':niceimageview')
|
||||
implementation project(path: ':iconloader')
|
||||
@@ -311,6 +315,7 @@ dependencies {
|
||||
implementation 'com.gitee.zackratos:UltimateBarX:0.8.0'
|
||||
//指示器
|
||||
implementation 'com.github.hackware1993:MagicIndicator:1.7.0'
|
||||
implementation 'com.opencsv:opencsv:5.5.2'
|
||||
|
||||
// 吐司框架:https://github.com/getActivity/Toaster
|
||||
implementation 'com.github.getActivity:Toaster:12.6'
|
||||
@@ -320,5 +325,4 @@ dependencies {
|
||||
//https://github.com/JessYanCoding/AndroidAutoSize
|
||||
implementation 'me.jessyan:autosize:1.2.1'
|
||||
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
BIN
app/libs/QWeather_Public_Android_V5.1.2.jar
Normal file
BIN
app/libs/QWeather_Public_Android_V5.1.2.jar
Normal file
Binary file not shown.
@@ -67,6 +67,10 @@
|
||||
android:name=".activity.app.AppListActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="portrait" />
|
||||
<activity android:name=".activity.weather.main.WeatherMainActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/AppThemeBlack"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<service
|
||||
android:name=".service.DialerAccessibilityService"
|
||||
@@ -105,10 +109,11 @@
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<!--baidu map-->
|
||||
<service
|
||||
android:name="com.baidu.location.f"
|
||||
android:enabled="true"
|
||||
android:process=":remote" />
|
||||
android:process=":location" />
|
||||
|
||||
<!--(jpush|jad)_config_start,jpush和jad公用的组件-->
|
||||
<!-- 可配置android:process参数将PushService放在其他进程中 -->
|
||||
|
||||
3579
app/src/main/assets/China-City-List-latest.csv
Normal file
3579
app/src/main/assets/China-City-List-latest.csv
Normal file
File diff suppressed because it is too large
Load Diff
@@ -33,7 +33,7 @@ import me.jessyan.autosize.AutoSize;
|
||||
|
||||
public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBinding> {
|
||||
private static final String TAG = "MainActivity";
|
||||
protected MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||
|
||||
private FragmentManager mFragmentManager = getSupportFragmentManager();
|
||||
// private BaseFragmentPagerAdapter mBaseFragmentPagerAdapter;
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.ttstd.dialer.activity.weather.main;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.baidu.location.BDLocation;
|
||||
import com.jeremyliao.liveeventbus.LiveEventBus;
|
||||
import com.qweather.sdk.response.weather.WeatherDaily;
|
||||
import com.qweather.sdk.response.weather.WeatherNow;
|
||||
import com.tencent.mmkv.MMKV;
|
||||
import com.ttstd.dialer.R;
|
||||
import com.ttstd.dialer.adapter.WeatherAdapter;
|
||||
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
|
||||
import com.ttstd.dialer.config.CommonConfig;
|
||||
import com.ttstd.dialer.databinding.ActivityWeatherMainBinding;
|
||||
import com.ttstd.dialer.utils.DateUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class WeatherMainActivity extends BaseMvvmActivity<WeatherMainViewModel, ActivityWeatherMainBinding> {
|
||||
private static final String TAG = "WeatherMainActivity";
|
||||
|
||||
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||
private WeatherAdapter mWeatherAdapter;
|
||||
|
||||
// @Override
|
||||
// public boolean setfitWindow() {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.activity_weather_main;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDataBinding() {
|
||||
mViewModel.setContext(this);
|
||||
mViewModel.setVDBinding(mViewDataBinding);
|
||||
mViewModel.setLifecycle(getLifecycleSubject());
|
||||
mViewDataBinding.setClick(new BtnClick());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView() {
|
||||
mWeatherAdapter = new WeatherAdapter();
|
||||
mViewDataBinding.recyclerView.setAdapter(mWeatherAdapter);
|
||||
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(WeatherMainActivity.this, LinearLayoutManager.VERTICAL, false);
|
||||
mViewDataBinding.recyclerView.setLayoutManager(linearLayoutManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initData() {
|
||||
Log.e(TAG, "initData: ");
|
||||
|
||||
mViewModel.mWeatherNowData.observe(this, new Observer<WeatherNow>() {
|
||||
@Override
|
||||
public void onChanged(WeatherNow weatherNow) {
|
||||
mViewDataBinding.setWeatherNow(weatherNow);
|
||||
}
|
||||
});
|
||||
mViewModel.mWeatherDailyListData.observe(this, new Observer<List<WeatherDaily>>() {
|
||||
@Override
|
||||
public void onChanged(List<WeatherDaily> weatherDailies) {
|
||||
Optional<WeatherDaily> dailyOptional = weatherDailies.stream().filter(new Predicate<WeatherDaily>() {
|
||||
@Override
|
||||
public boolean test(WeatherDaily weatherDaily) {
|
||||
return DateUtil.isTodayAll(weatherDaily.getFxDate());
|
||||
}
|
||||
}).findAny();
|
||||
if (dailyOptional.isPresent()) {
|
||||
WeatherDaily weatherDaily = dailyOptional.get();
|
||||
mViewDataBinding.tvTempRange.setText("最高" + weatherDaily.getTempMax() + "°" + "\t" + "最低" + weatherDaily.getTempMin() + "°");
|
||||
} else {
|
||||
|
||||
}
|
||||
mWeatherAdapter.setWeatherDailyList(weatherDailies);
|
||||
}
|
||||
});
|
||||
|
||||
boolean manually = mMMKV.decodeBool(CommonConfig.MANUALLY_SELECT_LOCATION, false);
|
||||
if (manually) {
|
||||
|
||||
} else {
|
||||
LiveEventBus.get(CommonConfig.LOCATION_CHANGED_KEY, BDLocation.class)
|
||||
.observeSticky(this, new Observer<BDLocation>() {
|
||||
@Override
|
||||
public void onChanged(BDLocation bdLocation) {
|
||||
mViewDataBinding.tvLocation.setText(bdLocation.getDistrict());
|
||||
mViewModel.getWeatherNow(bdLocation.getAdCode());
|
||||
mViewModel.getWeather10D(bdLocation.getAdCode());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class BtnClick {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.ttstd.dialer.activity.weather.main;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.qweather.sdk.Callback;
|
||||
import com.qweather.sdk.response.error.ErrorResponse;
|
||||
import com.qweather.sdk.response.weather.WeatherDaily;
|
||||
import com.qweather.sdk.response.weather.WeatherDailyResponse;
|
||||
import com.qweather.sdk.response.weather.WeatherNow;
|
||||
import com.qweather.sdk.response.weather.WeatherNowResponse;
|
||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||
import com.ttstd.dialer.base.mvvm.BaseViewModel;
|
||||
import com.ttstd.dialer.databinding.ActivityWeatherMainBinding;
|
||||
import com.ttstd.dialer.gson.GsonUtils;
|
||||
import com.ttstd.dialer.manager.WeatherManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WeatherMainViewModel extends BaseViewModel<ActivityWeatherMainBinding, ActivityEvent> {
|
||||
private static final String TAG = "WeatherMainViewModel";
|
||||
|
||||
public MutableLiveData<WeatherNow> mWeatherNowData = new MutableLiveData<>();
|
||||
|
||||
public void getWeatherNow(String adCode) {
|
||||
Log.e(TAG, "getWeatherNow: " + adCode);
|
||||
WeatherManager.getInstance().getWeatherNow(adCode, new Callback<WeatherNowResponse>() {
|
||||
@Override
|
||||
public void onSuccess(WeatherNowResponse response) {
|
||||
Log.e("getWeatherNow", "onSuccess: " + response);
|
||||
WeatherNow weatherNow = response.getNow();
|
||||
Log.e("getWeatherNow", "onSuccess: " + weatherNow);
|
||||
mWeatherNowData.postValue(weatherNow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ErrorResponse errorResponse) {
|
||||
Log.e("getWeatherNow", "onFailure: " + errorResponse.getError().getStatus());
|
||||
Log.e("getWeatherNow", "onFailure: " + errorResponse.getError().getDetail());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Throwable e) {
|
||||
Log.e("getWeatherNow", "onException: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public MutableLiveData<List<WeatherDaily>> mWeatherDailyListData = new MutableLiveData<>();
|
||||
|
||||
public void getWeather10D(String adCode) {
|
||||
Log.e(TAG, "getWeather10D: " + adCode);
|
||||
WeatherManager.getInstance().getWeather10D(adCode, new Callback<WeatherDailyResponse>() {
|
||||
@Override
|
||||
public void onSuccess(WeatherDailyResponse weatherDailyResponse) {
|
||||
Log.e("getWeather10D", "onSuccess: ");
|
||||
List<WeatherDaily> weatherDailyList = weatherDailyResponse.getDaily();
|
||||
Log.e("getWeather10D", "onSuccess: "+ GsonUtils.toJSONString(weatherDailyList));
|
||||
mWeatherDailyListData.postValue(weatherDailyList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ErrorResponse errorResponse) {
|
||||
Log.e("getWeather10D", "onFailure: ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Throwable throwable) {
|
||||
Log.e("getWeather10D", "onException: " + throwable.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.ttstd.dialer.adapter;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.qweather.sdk.response.weather.WeatherDaily;
|
||||
import com.tencent.mmkv.MMKV;
|
||||
import com.ttstd.dialer.R;
|
||||
import com.ttstd.dialer.config.CommonConfig;
|
||||
import com.ttstd.dialer.utils.DateUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class WeatherAdapter extends RecyclerView.Adapter<WeatherAdapter.WeatherHolder> {
|
||||
private static final String TAG = "WeatherAdapter";
|
||||
|
||||
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||
|
||||
private FragmentActivity mContext;
|
||||
private List<WeatherDaily> mWeatherDailyList;
|
||||
|
||||
public void setWeatherDailyList(List<WeatherDaily> weatherDailyList) {
|
||||
mWeatherDailyList = weatherDailyList;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeatherHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
mContext = (FragmentActivity) parent.getContext();
|
||||
return new WeatherHolder(LayoutInflater.from(mContext).inflate(R.layout.item_weather, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(WeatherHolder holder, int position) {
|
||||
WeatherDaily weatherDaily = mWeatherDailyList.get(position);
|
||||
String date = weatherDaily.getFxDate();
|
||||
if (DateUtil.isTodayAll(date)) {
|
||||
holder.tv_date.setText("今天");
|
||||
} else {
|
||||
String week = DateUtil.convertToChineseWeekdayAll(date);
|
||||
holder.tv_date.setText(week);
|
||||
}
|
||||
|
||||
String tempMin = weatherDaily.getTempMin();
|
||||
holder.tv_temp_min.setText(tempMin + "°");
|
||||
String tempMax = weatherDaily.getTempMax();
|
||||
holder.tv_temp_max.setText(tempMax + "°");
|
||||
|
||||
String iconDay = weatherDaily.getIconDay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mWeatherDailyList == null ? 0 : mWeatherDailyList.size();
|
||||
}
|
||||
|
||||
public class WeatherHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
TextView tv_date, tv_temp_min, tv_temp_max;
|
||||
ImageView iv_icon;
|
||||
|
||||
public WeatherHolder(View itemView) {
|
||||
super(itemView);
|
||||
tv_date = itemView.findViewById(R.id.tv_date);
|
||||
tv_temp_min = itemView.findViewById(R.id.tv_temp_min);
|
||||
tv_temp_max = itemView.findViewById(R.id.tv_temp_max);
|
||||
iv_icon = itemView.findViewById(R.id.iv_icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,13 @@ import android.util.Log;
|
||||
import com.alibaba.android.arouter.launcher.ARouter;
|
||||
import com.arialyy.aria.core.Aria;
|
||||
import com.hjq.toast.Toaster;
|
||||
import com.qweather.sdk.view.HeConfig;
|
||||
import com.tencent.bugly.crashreport.CrashReport;
|
||||
import com.tencent.mmkv.MMKV;
|
||||
import com.ttstd.dialer.BuildConfig;
|
||||
import com.ttstd.dialer.manager.AppManager;
|
||||
import com.ttstd.dialer.manager.MapManager;
|
||||
import com.ttstd.dialer.manager.WeatherManager;
|
||||
import com.ttstd.dialer.utils.Logger;
|
||||
import com.ttstd.dialer.utils.SystemUtils;
|
||||
import com.ttstd.iconloader.IconCacheManager;
|
||||
|
||||
@@ -55,6 +57,9 @@ public class BaseApplication extends Application {
|
||||
private void init() {
|
||||
Log.e(TAG, "init: ");
|
||||
if (SystemUtils.isMainProcessName(this, android.os.Process.myPid())) {
|
||||
Logger.initialize(this, BuildConfig.DEBUG);
|
||||
Logger.setLogLevel(Logger.LogLevel.DEBUG); // 开发阶段记录所有日志
|
||||
|
||||
String rootDir = MMKV.initialize(this);
|
||||
Log.e(TAG, "mmkv root: " + rootDir);
|
||||
|
||||
@@ -71,17 +76,13 @@ public class BaseApplication extends Application {
|
||||
return;
|
||||
}
|
||||
JPushInterface.init(this);
|
||||
JPushInterface.setAlias(this, );
|
||||
JPushInterface.setAlias(this, 0, SystemUtils.getSerial());
|
||||
|
||||
// 调整点二:App用户同意了隐私政策授权,并且开发者确定要开启推送服务后调用
|
||||
// JCore 5.0.4+会自动处理授权状态,可不需要显式设置true
|
||||
JCollectionAuth.setAuth(context, true);
|
||||
/*jpush end*/
|
||||
|
||||
HeConfig.init(BuildConfig.QweatherId, BuildConfig.QweatherKey);
|
||||
//切换至免费订阅
|
||||
HeConfig.switchToDevService();
|
||||
|
||||
if (BuildConfig.DEBUG) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
|
||||
ARouter.openLog(); // 打印日志
|
||||
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
|
||||
@@ -98,6 +99,9 @@ public class BaseApplication extends Application {
|
||||
xcrash.XCrash.init(this);
|
||||
|
||||
AppManager.init(this);
|
||||
MapManager.init(this);
|
||||
MapManager.getInstance().initMap();
|
||||
WeatherManager.init(this);
|
||||
IconCacheManager.init(this);
|
||||
}
|
||||
}
|
||||
|
||||
158
app/src/main/java/com/ttstd/dialer/bean/CityInfo.java
Normal file
158
app/src/main/java/com/ttstd/dialer/bean/CityInfo.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package com.ttstd.dialer.bean;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.opencsv.bean.CsvBindByName;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class CityInfo implements Serializable {
|
||||
@CsvBindByName(column = "Location_ID")
|
||||
String Location_ID;
|
||||
@CsvBindByName(column = "Location_Name_EN")
|
||||
String Location_Name_EN;
|
||||
@CsvBindByName(column = "Location_Name_ZH")
|
||||
String Location_Name_ZH;
|
||||
@CsvBindByName(column = "ISO_3166_1")
|
||||
String ISO_3166_1;
|
||||
@CsvBindByName(column = "Country_Region_EN")
|
||||
String Country_Region_EN;
|
||||
@CsvBindByName(column = "Country_Region_ZH")
|
||||
String Country_Region_ZH;
|
||||
@CsvBindByName(column = "Adm1_Name_EN")
|
||||
String Adm1_Name_EN;
|
||||
@CsvBindByName(column = "Adm1_Name_ZH")
|
||||
String Adm1_Name_ZH;
|
||||
@CsvBindByName(column = "Adm2_Name_EN")
|
||||
String Adm2_Name_EN;
|
||||
@CsvBindByName(column = "Adm2_Name_ZH")
|
||||
String Adm2_Name_ZH;
|
||||
@CsvBindByName(column = "Timezone")
|
||||
String Timezone;
|
||||
@CsvBindByName(column = "Latitude")
|
||||
String Latitude;
|
||||
@CsvBindByName(column = "Longitude")
|
||||
String Longitude;
|
||||
@CsvBindByName(column = "AD_code")
|
||||
String AD_code;
|
||||
|
||||
public String getLocation_ID() {
|
||||
return Location_ID;
|
||||
}
|
||||
|
||||
public void setLocation_ID(String location_ID) {
|
||||
Location_ID = location_ID;
|
||||
}
|
||||
|
||||
public String getLocation_Name_EN() {
|
||||
return Location_Name_EN;
|
||||
}
|
||||
|
||||
public void setLocation_Name_EN(String location_Name_EN) {
|
||||
Location_Name_EN = location_Name_EN;
|
||||
}
|
||||
|
||||
public String getLocation_Name_ZH() {
|
||||
return Location_Name_ZH;
|
||||
}
|
||||
|
||||
public void setLocation_Name_ZH(String location_Name_ZH) {
|
||||
Location_Name_ZH = location_Name_ZH;
|
||||
}
|
||||
|
||||
public String getISO_3166_1() {
|
||||
return ISO_3166_1;
|
||||
}
|
||||
|
||||
public void setISO_3166_1(String ISO_3166_1) {
|
||||
this.ISO_3166_1 = ISO_3166_1;
|
||||
}
|
||||
|
||||
public String getCountry_Region_EN() {
|
||||
return Country_Region_EN;
|
||||
}
|
||||
|
||||
public void setCountry_Region_EN(String country_Region_EN) {
|
||||
Country_Region_EN = country_Region_EN;
|
||||
}
|
||||
|
||||
public String getCountry_Region_ZH() {
|
||||
return Country_Region_ZH;
|
||||
}
|
||||
|
||||
public void setCountry_Region_ZH(String country_Region_ZH) {
|
||||
Country_Region_ZH = country_Region_ZH;
|
||||
}
|
||||
|
||||
public String getAdm1_Name_EN() {
|
||||
return Adm1_Name_EN;
|
||||
}
|
||||
|
||||
public void setAdm1_Name_EN(String adm1_Name_EN) {
|
||||
Adm1_Name_EN = adm1_Name_EN;
|
||||
}
|
||||
|
||||
public String getAdm1_Name_ZH() {
|
||||
return Adm1_Name_ZH;
|
||||
}
|
||||
|
||||
public void setAdm1_Name_ZH(String adm1_Name_ZH) {
|
||||
Adm1_Name_ZH = adm1_Name_ZH;
|
||||
}
|
||||
|
||||
public String getAdm2_Name_EN() {
|
||||
return Adm2_Name_EN;
|
||||
}
|
||||
|
||||
public void setAdm2_Name_EN(String adm2_Name_EN) {
|
||||
Adm2_Name_EN = adm2_Name_EN;
|
||||
}
|
||||
|
||||
public String getAdm2_Name_ZH() {
|
||||
return Adm2_Name_ZH;
|
||||
}
|
||||
|
||||
public void setAdm2_Name_ZH(String adm2_Name_ZH) {
|
||||
Adm2_Name_ZH = adm2_Name_ZH;
|
||||
}
|
||||
|
||||
public String getTimezone() {
|
||||
return Timezone;
|
||||
}
|
||||
|
||||
public void setTimezone(String timezone) {
|
||||
Timezone = timezone;
|
||||
}
|
||||
|
||||
public String getLatitude() {
|
||||
return Latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(String latitude) {
|
||||
Latitude = latitude;
|
||||
}
|
||||
|
||||
public String getLongitude() {
|
||||
return Longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(String longitude) {
|
||||
Longitude = longitude;
|
||||
}
|
||||
|
||||
public String getAD_code() {
|
||||
return AD_code;
|
||||
}
|
||||
|
||||
public void setAD_code(String AD_code) {
|
||||
this.AD_code = AD_code;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return JsonParser.parseString(new Gson().toJson(this)).getAsJsonObject().toString();
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,32 @@ public class CommonConfig {
|
||||
|
||||
public static final String CONTACT_HOME_PAGE = "contact_home_page_key";
|
||||
|
||||
public static final String MANUALLY_SELECT_LOCATION = "manually_select_location_key";
|
||||
|
||||
/*地址改变时发送*/
|
||||
public static final String LOCATION_CHANGED_KEY = "map_location_changed";
|
||||
public static final String LOCATION_ID_CHANGED_KEY = "map_Location_ID_changed";
|
||||
|
||||
/*当前定位地址*/
|
||||
public static final String CURRENT_LOCATION_MAP_ADDRESS_KEY = "current_map_location_map_address";
|
||||
public static final String CURRENT_LOCATION_MAP_LOCATION_DESCRIBE_KEY = "current_map_location_map_location_describe";
|
||||
/*省*/
|
||||
public static final String CURRENT_LOCATION_PROVINCE_KEY = "current_map_location_province";
|
||||
/*市*/
|
||||
public static final String CURRENT_LOCATION_CITY_KEY = "current_map_location_city";
|
||||
/*区*/
|
||||
public static final String CURRENT_LOCATION_DISTRICT_KEY = "current_map_location_district";
|
||||
public static final String CURRENT_LOCATION_TOWN_KEY = "current_map_location_town";
|
||||
public static final String CURRENT_LOCATION_STREET_KEY = "current_map_location_street";
|
||||
|
||||
/*当前定位经度*/
|
||||
public static final String CURRENT_LOCATION_LONGITUDE_KEY = "current_map_location_longitude";
|
||||
/*当前定位纬度*/
|
||||
public static final String CURRENT_LOCATION_LATITUDE_KEY = "current_map_location_latitude";
|
||||
/*当前ad_code*/
|
||||
public static final String CURRENT_LOCATION_AD_CODE_KEY = "current_map_location_ad_code";
|
||||
/*当前LocationID*/
|
||||
@Deprecated
|
||||
public static final String CURRENT_LOCATION_ID_KEY = "current_map_location_ID";
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
@@ -17,10 +16,11 @@ import androidx.fragment.app.Fragment;
|
||||
import com.hjq.toast.Toaster;
|
||||
import com.ttstd.dialer.R;
|
||||
import com.ttstd.dialer.activity.contact.list.ContactListActivity;
|
||||
import com.ttstd.dialer.activity.weather.main.WeatherMainActivity;
|
||||
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmFragment;
|
||||
import com.ttstd.dialer.databinding.FragmentHomeBinding;
|
||||
import com.ttstd.dialer.utils.ApkUtils;
|
||||
import com.ttstd.dialer.utils.DataUtil;
|
||||
import com.ttstd.dialer.utils.DateUtil;
|
||||
import com.ttstd.dialer.utils.LunarCalendarFestivalUtils;
|
||||
|
||||
/**
|
||||
@@ -166,7 +166,7 @@ public class HomeFragment extends BaseMvvmFragment<HomeViewModel, FragmentHomeBi
|
||||
|
||||
private void setTime() {
|
||||
if (isAdded()) {
|
||||
mViewDataBinding.tvTime.setText(DataUtil.formatDateHour());
|
||||
mViewDataBinding.tvTime.setText(DateUtil.formatDateHour());
|
||||
// mViewDataBinding.tvDate.setText(DataUtil.formatDateDay());
|
||||
// mViewDataBinding.tvWeek.setText(TimeUtils.getWeek());
|
||||
// mViewDataBinding.tvLunar.setText(mFestivalUtils.getLunarCalendar());
|
||||
@@ -220,6 +220,9 @@ public class HomeFragment extends BaseMvvmFragment<HomeViewModel, FragmentHomeBi
|
||||
}
|
||||
}
|
||||
|
||||
public void openWeather(View view) {
|
||||
startActivity(new Intent(mContext, WeatherMainActivity.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -6,4 +6,5 @@ import com.ttstd.dialer.databinding.FragmentHomeBinding;
|
||||
|
||||
public class HomeViewModel extends BaseViewModel<FragmentHomeBinding, FragmentEvent> {
|
||||
|
||||
|
||||
}
|
||||
|
||||
102
app/src/main/java/com/ttstd/dialer/manager/CsvDeserializer.java
Normal file
102
app/src/main/java/com/ttstd/dialer/manager/CsvDeserializer.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package com.ttstd.dialer.manager;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.opencsv.bean.CsvToBean;
|
||||
import com.opencsv.bean.CsvToBeanBuilder;
|
||||
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
|
||||
import com.ttstd.dialer.bean.CityInfo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CsvDeserializer {
|
||||
private static final String TAG = "CsvDeserializer";
|
||||
|
||||
/**
|
||||
* 从assets文件夹中的CSV文件反序列化为对象列表
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param fileName assets中的CSV文件名
|
||||
* @return 对象列表
|
||||
*/
|
||||
public static List<CityInfo> deserializeFromAssets(Context context, String fileName) {
|
||||
InputStream inputStream = null;
|
||||
Reader reader = null;
|
||||
try {
|
||||
long time = System.currentTimeMillis();
|
||||
// 从assets获取CSV文件输入流
|
||||
inputStream = context.getAssets().open(fileName);
|
||||
reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
|
||||
// 创建映射策略,使用CSV头部列名映射到对象字段
|
||||
HeaderColumnNameMappingStrategy<CityInfo> strategy = new HeaderColumnNameMappingStrategy<>();
|
||||
strategy.setType(CityInfo.class);
|
||||
|
||||
// 创建CsvToBean并进行转换
|
||||
CsvToBean<CityInfo> csvToBean = new CsvToBeanBuilder<CityInfo>(reader)
|
||||
.withMappingStrategy(strategy)
|
||||
.withIgnoreLeadingWhiteSpace(true)
|
||||
.withIgnoreEmptyLine(true) // 忽略空行
|
||||
.withThrowExceptions(false) // 不抛出异常,便于调试
|
||||
.build();
|
||||
Log.e(TAG, "deserializeFromAssets: finish " + (System.currentTimeMillis() - time) + "ms");
|
||||
// 转换并返回结果列表
|
||||
return csvToBean.parse();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error reading CSV file: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error during CSV deserialization: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
// 确保关闭流,避免资源泄漏
|
||||
try {
|
||||
if (reader != null) {
|
||||
reader.close();
|
||||
}
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to close stream: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从输入流反序列化为对象列表
|
||||
*
|
||||
* @param inputStream CSV文件输入流
|
||||
* @return 对象列表
|
||||
*/
|
||||
public static List<CityInfo> deserializeFromStream(InputStream inputStream) {
|
||||
try {
|
||||
Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
|
||||
HeaderColumnNameMappingStrategy<CityInfo> strategy = new HeaderColumnNameMappingStrategy<>();
|
||||
strategy.setType(CityInfo.class);
|
||||
|
||||
CsvToBean<CityInfo> csvToBean = new CsvToBeanBuilder<CityInfo>(reader)
|
||||
.withMappingStrategy(strategy)
|
||||
.withIgnoreLeadingWhiteSpace(true)
|
||||
.build();
|
||||
|
||||
return csvToBean.parse();
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error during CSV deserialization: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
363
app/src/main/java/com/ttstd/dialer/manager/MapManager.java
Normal file
363
app/src/main/java/com/ttstd/dialer/manager/MapManager.java
Normal file
@@ -0,0 +1,363 @@
|
||||
package com.ttstd.dialer.manager;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import com.baidu.location.BDAbstractLocationListener;
|
||||
import com.baidu.location.BDLocation;
|
||||
import com.baidu.location.LocationClient;
|
||||
import com.baidu.location.LocationClientOption;
|
||||
import com.jeremyliao.liveeventbus.LiveEventBus;
|
||||
import com.tencent.mmkv.MMKV;
|
||||
import com.ttstd.dialer.config.CommonConfig;
|
||||
import com.ttstd.dialer.gson.GsonUtils;
|
||||
import com.ttstd.dialer.utils.Logger;
|
||||
import com.ttstd.dialer.utils.SystemUtils;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class MapManager {
|
||||
private static final String TAG = "MapManager";
|
||||
private static final String[] REQUIRED_PERMISSIONS = {
|
||||
android.Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
android.Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
};
|
||||
|
||||
// 线程安全的单例模式(双重检查锁)
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static volatile MapManager sInstance;
|
||||
private final Context mContext; // 使用Application Context避免内存泄漏
|
||||
private final MMKV mMMKV;
|
||||
private LocationClient mLocationClient;
|
||||
private LocationClientOption mOption;
|
||||
// 线程安全的回调列表
|
||||
private final List<OnLocationListener> mListeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
// 定位结果回调接口
|
||||
public interface OnLocationListener {
|
||||
void onLocationSuccess(BDLocation location); // 定位成功
|
||||
|
||||
void onLocationFailure(String errorMsg); // 定位失败
|
||||
}
|
||||
|
||||
private MapManager(Context context) {
|
||||
// 使用Application Context,避免持有Activity导致内存泄漏
|
||||
this.mContext = context.getApplicationContext();
|
||||
this.mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||
}
|
||||
|
||||
// 初始化单例(线程安全)
|
||||
public static void init(Context context) {
|
||||
if (context == null) {
|
||||
throw new RuntimeException("Context cannot be null");
|
||||
}
|
||||
if (sInstance == null) {
|
||||
synchronized (MapManager.class) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new MapManager(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static MapManager getInstance() {
|
||||
if (sInstance == null) {
|
||||
throw new IllegalStateException("MapManager must be initialized first with init(Context)");
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化定位客户端(包含权限检查)
|
||||
*/
|
||||
public void initMap() {
|
||||
if (!checkLocationPermissions()) {
|
||||
Logger.e(TAG, "Location permissions are missing");
|
||||
notifyLocationFailure("缺少定位权限,请先授予权限");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 设置系统定位参数(抽取为独立方法)
|
||||
setupSystemLocationSettings();
|
||||
} catch (Exception e) {
|
||||
Logger.e(TAG, "Failed to setup system location settings", e.getMessage());
|
||||
notifyLocationFailure("系统定位设置失败:" + e.getMessage());
|
||||
}
|
||||
LocationClient.setAgreePrivacy(true);
|
||||
if (mLocationClient == null) {
|
||||
try {
|
||||
mLocationClient = new LocationClient(mContext);
|
||||
mLocationClient.setLocOption(getDefaultLocationClientOption());
|
||||
mLocationClient.registerLocationListener(mLocationListener);
|
||||
// 确保先停止再启动,避免重复启动
|
||||
if (mLocationClient.isStarted()) {
|
||||
mLocationClient.stop();
|
||||
}
|
||||
mLocationClient.start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Logger.e(TAG, "initMap: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查定位权限
|
||||
*/
|
||||
private boolean checkLocationPermissions() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return true; // 6.0以下默认有权限
|
||||
}
|
||||
for (String permission : REQUIRED_PERMISSIONS) {
|
||||
if (ActivityCompat.checkSelfPermission(mContext, permission)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置系统定位参数(抽取重复代码)
|
||||
*/
|
||||
private void setupSystemLocationSettings() {
|
||||
// Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.ASSISTED_GPS_ENABLED, 1);
|
||||
Settings.Global.putInt(mContext.getContentResolver(), "assisted_gps_enabled", 1);
|
||||
// 合并定位提供者设置(原代码重复设置会覆盖,改为追加)
|
||||
String providers = Settings.Secure.getString(mContext.getContentResolver(),
|
||||
Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
|
||||
if (TextUtils.isEmpty(providers)) {
|
||||
providers = "gps,network";
|
||||
} else {
|
||||
if (!providers.contains("gps")) providers += ",gps";
|
||||
if (!providers.contains("network")) providers += ",network";
|
||||
}
|
||||
Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED, providers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取定位客户端(懒加载)
|
||||
*/
|
||||
public LocationClient getLocationClient() {
|
||||
if (mLocationClient == null) {
|
||||
initMap();
|
||||
}
|
||||
return mLocationClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置定位参数(允许外部自定义)
|
||||
*/
|
||||
public void setLocationOption(LocationClientOption option) {
|
||||
if (option != null && mLocationClient != null) {
|
||||
mOption = option;
|
||||
mLocationClient.setLocOption(option);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认定位参数配置(修复重复设置问题)
|
||||
*/
|
||||
public LocationClientOption getDefaultLocationClientOption() {
|
||||
if (mOption == null) {
|
||||
mOption = new LocationClientOption();
|
||||
mOption.setCoorType("bd09ll"); // 百度坐标系(配合百度地图使用)
|
||||
mOption.setScanSpan(0); // 仅定位一次(0表示单次定位)
|
||||
mOption.setIsNeedAddress(true); // 需要地址信息
|
||||
mOption.setIsNeedLocationDescribe(true); // 需要地址描述
|
||||
mOption.setNeedDeviceDirect(false); // 不需要设备方向
|
||||
mOption.setLocationNotify(false); // 不频繁输出GPS结果
|
||||
mOption.setIgnoreKillProcess(true); // 定位服务独立进程
|
||||
mOption.setIsNeedLocationPoiList(true); // 需要POI结果
|
||||
mOption.SetIgnoreCacheException(false); // 收集错误信息
|
||||
mOption.setLocationMode(LocationClientOption.LocationMode.Battery_Saving); // 低功耗模式
|
||||
mOption.setIsNeedAltitude(false); // 不需要海拔信息
|
||||
}
|
||||
return mOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册定位回调
|
||||
*/
|
||||
public void registerLocationListener(OnLocationListener listener) {
|
||||
if (listener != null && !mListeners.contains(listener)) {
|
||||
mListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 反注册定位回调(避免内存泄漏)
|
||||
*/
|
||||
public void unregisterLocationListener(OnLocationListener listener) {
|
||||
if (listener != null) {
|
||||
mListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知所有监听器定位成功
|
||||
*/
|
||||
private void notifyLocationSuccess(BDLocation location) {
|
||||
for (OnLocationListener listener : mListeners) {
|
||||
try {
|
||||
listener.onLocationSuccess(location);
|
||||
} catch (Exception e) {
|
||||
Logger.e(TAG, "Error in onLocationSuccess callback", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知所有监听器定位失败
|
||||
*/
|
||||
private void notifyLocationFailure(String errorMsg) {
|
||||
for (OnLocationListener listener : mListeners) {
|
||||
try {
|
||||
listener.onLocationFailure(errorMsg);
|
||||
} catch (Exception e) {
|
||||
Logger.e(TAG, "Error in onLocationFailure callback", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储定位结果(解耦存储逻辑)
|
||||
*/
|
||||
private void saveLocationResult(BDLocation location) {
|
||||
if (location == null) return;
|
||||
mMMKV.encode(CommonConfig.CURRENT_LOCATION_MAP_ADDRESS_KEY, location.getAddrStr());
|
||||
mMMKV.encode(CommonConfig.CURRENT_LOCATION_MAP_LOCATION_DESCRIBE_KEY, location.getLocationDescribe());
|
||||
mMMKV.encode(CommonConfig.CURRENT_LOCATION_PROVINCE_KEY, location.getProvince());
|
||||
mMMKV.encode(CommonConfig.CURRENT_LOCATION_CITY_KEY, location.getCity());
|
||||
mMMKV.encode(CommonConfig.CURRENT_LOCATION_DISTRICT_KEY, location.getDistrict());
|
||||
mMMKV.encode(CommonConfig.CURRENT_LOCATION_TOWN_KEY, location.getTown());
|
||||
mMMKV.encode(CommonConfig.CURRENT_LOCATION_STREET_KEY, location.getStreet());
|
||||
|
||||
mMMKV.encode(CommonConfig.CURRENT_LOCATION_LONGITUDE_KEY, location.getLongitude());
|
||||
mMMKV.encode(CommonConfig.CURRENT_LOCATION_LATITUDE_KEY, location.getLatitude());
|
||||
mMMKV.encode(CommonConfig.CURRENT_LOCATION_AD_CODE_KEY, location.getAdCode());
|
||||
|
||||
// mMMKV.encode(CommonConfig.CURRENT_LOCATION_LOCATION_ID_KEY, location.getLocationID());
|
||||
|
||||
mMMKV.encode("MapError", "-");
|
||||
|
||||
Map<String, Object> params = new LinkedHashMap<>();
|
||||
params.put("sn", SystemUtils.getSerial());
|
||||
params.put("address", location.getAddrStr());
|
||||
params.put("location_describe", location.getLocationDescribe());
|
||||
params.put("longitude", location.getLongitude());
|
||||
params.put("latitude", location.getLatitude());
|
||||
params.put("ad_code", location.getAdCode());
|
||||
params.put("coor_type", location.getCoorType());
|
||||
|
||||
Logger.e(TAG, "saveLocationResult", "location = " + GsonUtils.toJSONString(location));
|
||||
|
||||
WeatherManager.getInstance().setAdCode(location.getAdCode());
|
||||
|
||||
LiveEventBus.get(CommonConfig.LOCATION_CHANGED_KEY)
|
||||
.post(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储定位错误信息
|
||||
*/
|
||||
private void saveLocationError(String errorMsg) {
|
||||
mMMKV.encode("MapError", errorMsg);
|
||||
}
|
||||
|
||||
// 百度定位回调
|
||||
private final BDAbstractLocationListener mLocationListener = new BDAbstractLocationListener() {
|
||||
@Override
|
||||
public void onReceiveLocation(BDLocation location) {
|
||||
if (location == null) {
|
||||
String error = "定位结果为空";
|
||||
Logger.e(TAG, "onReceiveLocation", error);
|
||||
saveLocationError(error);
|
||||
notifyLocationFailure(error);
|
||||
stopLocation();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (location.getLocType()) {
|
||||
case BDLocation.TypeGpsLocation:
|
||||
case BDLocation.TypeNetWorkLocation:
|
||||
case BDLocation.TypeOffLineLocation:
|
||||
Logger.e(TAG, "onReceiveLocation", "定位成功: " + location.getAddrStr() + location.getLocationDescribe());
|
||||
saveLocationResult(location);
|
||||
notifyLocationSuccess(location);
|
||||
break;
|
||||
case BDLocation.TypeServerError:
|
||||
String serverError = "服务端定位失败,请反馈问题";
|
||||
Logger.e(TAG, "onReceiveLocation", serverError);
|
||||
saveLocationError(serverError);
|
||||
notifyLocationFailure(serverError);
|
||||
break;
|
||||
case BDLocation.TypeNetWorkException:
|
||||
String networkError = "网络异常导致定位失败,请检查网络";
|
||||
Logger.e(TAG, "onReceiveLocation", networkError);
|
||||
saveLocationError(networkError);
|
||||
notifyLocationFailure(networkError);
|
||||
break;
|
||||
case BDLocation.TypeCriteriaException:
|
||||
String criteriaError = "无法获取定位依据,请检查设备状态";
|
||||
Logger.e(TAG, "onReceiveLocation", criteriaError);
|
||||
saveLocationError(criteriaError);
|
||||
notifyLocationFailure(criteriaError);
|
||||
break;
|
||||
default:
|
||||
String unknownError = "未知定位错误: " + location.getLocType();
|
||||
Logger.e(TAG, "onReceiveLocation", unknownError);
|
||||
saveLocationError(unknownError);
|
||||
notifyLocationFailure(unknownError);
|
||||
break;
|
||||
}
|
||||
|
||||
// 单次定位完成后停止(如果是单次定位模式)
|
||||
if (mOption != null && mOption.getScanSpan() == 0) {
|
||||
stopLocation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocDiagnosticMessage(int locType, int diagnosticType, String diagnosticMessage) {
|
||||
super.onLocDiagnosticMessage(locType, diagnosticType, diagnosticMessage);
|
||||
String errorMsg = "定位诊断: " + diagnosticMessage;
|
||||
Logger.e(TAG, "onLocDiagnosticMessage", errorMsg);
|
||||
saveLocationError(errorMsg);
|
||||
notifyLocationFailure(errorMsg);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 停止定位并释放资源
|
||||
*/
|
||||
public void stopLocation() {
|
||||
if (mLocationClient != null && mLocationClient.isStarted()) {
|
||||
mLocationClient.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁定位客户端(建议在Application退出时调用)
|
||||
*/
|
||||
public void destroy() {
|
||||
stopLocation();
|
||||
if (mLocationClient != null) {
|
||||
mLocationClient.unRegisterLocationListener(mLocationListener);
|
||||
mLocationClient = null;
|
||||
}
|
||||
mListeners.clear(); // 清除所有回调,避免内存泄漏
|
||||
}
|
||||
}
|
||||
171
app/src/main/java/com/ttstd/dialer/manager/WeatherManager.java
Normal file
171
app/src/main/java/com/ttstd/dialer/manager/WeatherManager.java
Normal file
@@ -0,0 +1,171 @@
|
||||
package com.ttstd.dialer.manager;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.jeremyliao.liveeventbus.LiveEventBus;
|
||||
import com.qweather.sdk.Callback;
|
||||
import com.qweather.sdk.JWTGenerator;
|
||||
import com.qweather.sdk.QWeather;
|
||||
import com.qweather.sdk.basic.Lang;
|
||||
import com.qweather.sdk.basic.Unit;
|
||||
import com.qweather.sdk.parameter.weather.WeatherParameter;
|
||||
import com.qweather.sdk.response.weather.WeatherDailyResponse;
|
||||
import com.qweather.sdk.response.weather.WeatherNowResponse;
|
||||
import com.ttstd.dialer.BuildConfig;
|
||||
import com.ttstd.dialer.bean.CityInfo;
|
||||
import com.ttstd.dialer.config.CommonConfig;
|
||||
import com.ttstd.dialer.utils.Logger;
|
||||
import com.ttstd.dialer.utils.NativeUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.annotations.NonNull;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.ObservableEmitter;
|
||||
import io.reactivex.rxjava3.core.ObservableOnSubscribe;
|
||||
import io.reactivex.rxjava3.core.Observer;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class WeatherManager {
|
||||
private static final String TAG = "WeatherManager";
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static WeatherManager INSTANCE;
|
||||
private Context mContext;
|
||||
private QWeather mQWeather;
|
||||
private Map<String, String> LocationIDMap = new HashMap<>();
|
||||
private boolean loadCsvFinish = false;
|
||||
private String mAdCode;
|
||||
|
||||
|
||||
private WeatherManager(Context context) {
|
||||
this.mContext = context.getApplicationContext();
|
||||
initCsv();
|
||||
try {
|
||||
// 通过SDK提供的JWTGenerator设置令牌生成器,其实现自TokenGenerator接口
|
||||
JWTGenerator jwt = new JWTGenerator(NativeUtils.getQWeatherPrivateKey(), // 私钥
|
||||
NativeUtils.getQWeatherProjectId(), // 项目ID
|
||||
NativeUtils.getQWeatherCredentialId()); // 凭据ID
|
||||
mQWeather = QWeather.getInstance(context, NativeUtils.getQWeatherUrl()) // 初始化服务地址
|
||||
.setLogEnable(BuildConfig.DEBUG) // 启用调试日志(生产环境建议设置为 false)
|
||||
.setTokenGenerator(jwt);
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "QWeatherUtils: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void init(Context context) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new WeatherManager(context);
|
||||
}
|
||||
}
|
||||
|
||||
public static WeatherManager getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
throw new IllegalStateException("You must be init WeatherManager first");
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public QWeather getQWeather() {
|
||||
return mQWeather;
|
||||
}
|
||||
|
||||
public void setAdCode(String adCode) {
|
||||
mAdCode = adCode;
|
||||
if (loadCsvFinish) {
|
||||
if (!TextUtils.isEmpty(adCode)) {
|
||||
String locationId = LocationIDMap.get(adCode);
|
||||
Logger.e(this, TAG, "setAdCode: locationId = " + locationId);
|
||||
LiveEventBus.get(CommonConfig.LOCATION_ID_CHANGED_KEY)
|
||||
.post(locationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initCsv() {
|
||||
Observable.create(new ObservableOnSubscribe<List<CityInfo>>() {
|
||||
@Override
|
||||
public void subscribe(@NonNull ObservableEmitter<List<CityInfo>> emitter) throws Throwable {
|
||||
long time = System.currentTimeMillis();
|
||||
List<CityInfo> cityInfos = CsvDeserializer.deserializeFromAssets(mContext, "China-City-List-latest.csv");
|
||||
Log.e(TAG, "subscribe: deserializeFromAssets time = " + (System.currentTimeMillis() - time) + "ms");
|
||||
emitter.onNext(cityInfos);
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Observer<List<CityInfo>>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
Logger.e(TAG, "initCsv", "onSubscribe: ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@NonNull List<CityInfo> cityInfos) {
|
||||
Logger.e(TAG, "initCsv", "onNext: cityInfos size = " + cityInfos.size());
|
||||
Logger.e(TAG, "initCsv", "onNext: cityInfos 0 info = " + cityInfos.get(0));
|
||||
|
||||
LocationIDMap = cityInfos.stream()
|
||||
.filter(cityInfo -> cityInfo.getLocation_ID() != null) // 过滤掉 value 为 null 的条目
|
||||
.collect(Collectors.toMap(CityInfo::getAD_code, CityInfo::getLocation_ID, (oldVal, newVal) -> oldVal, HashMap::new));
|
||||
|
||||
Logger.e(TAG, "initCsv", "onNext: LocationIDMap size = " + LocationIDMap.size());
|
||||
onComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Logger.e(TAG, "initCsv", "onError: " + e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Logger.e(TAG, "initCsv", "onComplete: ");
|
||||
loadCsvFinish = true;
|
||||
if (!TextUtils.isEmpty(mAdCode)) {
|
||||
String locationId = LocationIDMap.get(mAdCode);
|
||||
Logger.e(TAG, "initCsv", "onComplete: locationId = " + locationId);
|
||||
LiveEventBus.get(CommonConfig.LOCATION_ID_CHANGED_KEY)
|
||||
.post(locationId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getLocationID(String adCode) {
|
||||
if (LocationIDMap != null) {
|
||||
return LocationIDMap.get(adCode);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public void getWeatherNow(String adCode, Callback<WeatherNowResponse> callback) {
|
||||
String locationID = getLocationID(adCode);
|
||||
Log.e(TAG, "getWeatherNow: locationID = " + locationID);
|
||||
WeatherParameter parameter = new WeatherParameter(locationID)
|
||||
.lang(Lang.ZH_HANS)
|
||||
.unit(Unit.METRIC);
|
||||
mQWeather.weatherNow(parameter, callback);
|
||||
}
|
||||
|
||||
public void getWeather10D(String adCode, Callback<WeatherDailyResponse> callback) {
|
||||
String locationID = getLocationID(adCode);
|
||||
Log.e(TAG, "getWeather10D: locationID = " + locationID);
|
||||
WeatherParameter parameter = new WeatherParameter(locationID)
|
||||
.lang(Lang.ZH_HANS)
|
||||
.unit(Unit.METRIC);
|
||||
mQWeather.weather10d(parameter, callback);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package com.ttstd.dialer.push.jpush;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import cn.jpush.android.api.CustomMessage;
|
||||
import cn.jpush.android.api.JPushMessage;
|
||||
import cn.jpush.android.service.JPushMessageReceiver;
|
||||
|
||||
public class PushMessageService extends JPushMessageReceiver {
|
||||
private static final String TAG = "PushMessageService";
|
||||
|
||||
@Override
|
||||
public void onMessage(Context context, CustomMessage customMessage) {
|
||||
@@ -16,5 +18,13 @@ public class PushMessageService extends JPushMessageReceiver {
|
||||
@Override
|
||||
public void onAliasOperatorResult(Context context, JPushMessage jPushMessage) {
|
||||
super.onAliasOperatorResult(context, jPushMessage);
|
||||
int errorCode = jPushMessage.getErrorCode();
|
||||
Log.e(TAG, "onAliasOperatorResult: " + errorCode);
|
||||
if (errorCode == 0) {
|
||||
Log.e(TAG, "onAliasOperatorResult: " + jPushMessage.getAlias());
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.ttstd.dialer.utils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class DataUtil {
|
||||
private static SimpleDateFormat day = new SimpleDateFormat("M月d日");
|
||||
private static SimpleDateFormat hour = new SimpleDateFormat("HH:mm");
|
||||
private static SimpleDateFormat minute = new SimpleDateFormat("mm");
|
||||
|
||||
/**
|
||||
* 格式化日期(精确到天)
|
||||
*/
|
||||
public static String formatDateDay() {
|
||||
return day.format(new Date());
|
||||
}
|
||||
|
||||
public static String formatDateTime() {
|
||||
return day.format(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期(hour)
|
||||
*/
|
||||
public static String formatDateHour() {
|
||||
return hour.format(new Date());
|
||||
}
|
||||
|
||||
}
|
||||
187
app/src/main/java/com/ttstd/dialer/utils/DateUtil.java
Normal file
187
app/src/main/java/com/ttstd/dialer/utils/DateUtil.java
Normal file
@@ -0,0 +1,187 @@
|
||||
package com.ttstd.dialer.utils;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.TextStyle;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public class DateUtil {
|
||||
private static SimpleDateFormat day = new SimpleDateFormat("M月d日");
|
||||
private static SimpleDateFormat hour = new SimpleDateFormat("HH:mm");
|
||||
private static SimpleDateFormat minute = new SimpleDateFormat("mm");
|
||||
|
||||
/**
|
||||
* 格式化日期(精确到天)
|
||||
*/
|
||||
public static String formatDateDay() {
|
||||
return day.format(new Date());
|
||||
}
|
||||
|
||||
public static String formatDateTime() {
|
||||
return day.format(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期(hour)
|
||||
*/
|
||||
public static String formatDateHour() {
|
||||
return hour.format(new Date());
|
||||
}
|
||||
|
||||
public static boolean isTodayAll(String dateStr) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return isToday26(dateStr);
|
||||
} else {
|
||||
return isToday(dateStr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isToday(String dateStr) {
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||
Date inputDate = sdf.parse(dateStr);
|
||||
|
||||
Calendar today = Calendar.getInstance();
|
||||
Calendar target = Calendar.getInstance();
|
||||
target.setTime(inputDate);
|
||||
|
||||
return today.get(Calendar.YEAR) == target.get(Calendar.YEAR) &&
|
||||
today.get(Calendar.MONTH) == target.get(Calendar.MONTH) &&
|
||||
today.get(Calendar.DAY_OF_MONTH) == target.get(Calendar.DAY_OF_MONTH);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static boolean isToday26(String dateStr) {
|
||||
try {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
LocalDate inputDate = LocalDate.parse(dateStr, formatter);
|
||||
LocalDate today = LocalDate.now();
|
||||
|
||||
return today.isEqual(inputDate);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getWeekDayFromDateAll(String dateStr, Locale locale) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return getWeekDayFromDate26(dateStr,locale);
|
||||
}else {
|
||||
return getWeekDayFromDate(dateStr,locale);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将 yyyy-MM-dd 格式的日期字符串转换为星期几
|
||||
* @param dateStr 日期字符串,格式:yyyy-MM-dd
|
||||
* @param locale 地区设置,如 Locale.CHINA
|
||||
* @return 星期几的中文表示(如:星期一)
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static String getWeekDayFromDate26(String dateStr, Locale locale) {
|
||||
// 定义日期格式
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
// 解析日期字符串
|
||||
LocalDate date = LocalDate.parse(dateStr, formatter);
|
||||
// 获取星期几并格式化为中文
|
||||
return date.getDayOfWeek().getDisplayName(TextStyle.FULL, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 yyyy-MM-dd 格式的日期字符串转换为星期几
|
||||
* @param dateStr 日期字符串,格式:yyyy-MM-dd
|
||||
* @param locale 地区设置,如 Locale.CHINA
|
||||
* @return 星期几的中文表示
|
||||
*/
|
||||
public static String getWeekDayFromDate(String dateStr, Locale locale) {
|
||||
try {
|
||||
// 定义日期格式
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", locale);
|
||||
Date date = sdf.parse(dateStr);
|
||||
|
||||
// 创建用于格式化星期的 SimpleDateFormat
|
||||
SimpleDateFormat weekFormat = new SimpleDateFormat("EEEE", locale);
|
||||
return weekFormat.format(date);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static String convertToChineseWeekdayAll(String dateStr) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return convertToChineseWeekdayWithLocalDate(dateStr);
|
||||
}else {
|
||||
return convertToChineseWeekday(dateStr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将yyyy-MM-dd格式的日期字符串转换为"周几"格式
|
||||
* 例如:2026-02-17 → 周二
|
||||
*/
|
||||
public static String convertToChineseWeekday(String dateStr) {
|
||||
try {
|
||||
// 解析日期
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);
|
||||
Date date = sdf.parse(dateStr);
|
||||
|
||||
if (date == null) {
|
||||
return "日期解析失败";
|
||||
}
|
||||
|
||||
// 使用Calendar获取星期几
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
|
||||
|
||||
// Calendar中周日=1,周一=2,...,周六=7
|
||||
// 转换为中文"周几"格式
|
||||
String[] weekDays = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
|
||||
|
||||
// 注意:Calendar的DAY_OF_WEEK从周日开始(1=周日),所以要减1
|
||||
return weekDays[dayOfWeek - 1];
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "日期格式错误";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 另一种实现方式:使用LocalDate(需要API 26+)
|
||||
* 推荐在Android 8.0及以上使用
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static String convertToChineseWeekdayWithLocalDate(String dateStr) {
|
||||
try {
|
||||
// 注意:以下代码需要Android API 26+(Android 8.0)
|
||||
java.time.LocalDate localDate = java.time.LocalDate.parse(dateStr);
|
||||
int dayOfWeekValue = localDate.getDayOfWeek().getValue();
|
||||
|
||||
// java.time中周一=1,周二=2,...,周日=7
|
||||
String[] weekDays = {"周一", "周二", "周三", "周四", "周五", "周六", "周日"};
|
||||
|
||||
return weekDays[dayOfWeekValue - 1];
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "日期格式错误";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
128
app/src/main/java/com/ttstd/dialer/utils/Logger.java
Normal file
128
app/src/main/java/com/ttstd/dialer/utils/Logger.java
Normal file
@@ -0,0 +1,128 @@
|
||||
package com.ttstd.dialer.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.ttstd.dialer.BuildConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public class Logger {
|
||||
// 日志级别枚举
|
||||
public enum LogLevel {
|
||||
DEBUG, INFO, WARN, ERROR
|
||||
}
|
||||
|
||||
private static final String TAG = "AppLogger";
|
||||
private static final String LOG_FILE_NAME = "app_log.txt";
|
||||
private static File logFile;
|
||||
private static LogLevel logLevel = LogLevel.DEBUG; // 默认日志级别
|
||||
private static boolean isDebugMode = BuildConfig.DEBUG; // 默认关闭Debug输出
|
||||
|
||||
// 初始化日志系统(需在应用启动时调用)
|
||||
public static void initialize(Context context, boolean debugMode) {
|
||||
isDebugMode = debugMode;
|
||||
try {
|
||||
File storageDir = context.getExternalFilesDir("log");
|
||||
logFile = new File(storageDir, LOG_FILE_NAME);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Log file init failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 设置全局日志级别
|
||||
public static void setLogLevel(LogLevel level) {
|
||||
logLevel = level;
|
||||
}
|
||||
|
||||
// 核心日志方法
|
||||
private static void log(LogLevel level, Object context, String name, String message) {
|
||||
if (level.ordinal() < logLevel.ordinal()) return; // 低于设定级别不记录
|
||||
String tag;
|
||||
if (TextUtils.isEmpty(context.getClass().getSimpleName())) {
|
||||
tag = context.toString();
|
||||
} else {
|
||||
tag = context.getClass().getSimpleName();
|
||||
}
|
||||
String logMsg = formatLog(level, tag, name, message);
|
||||
writeToFile(logMsg); // 始终写入文件
|
||||
|
||||
if (isDebugMode) {
|
||||
outputToConsole(level, tag, name, message); // 仅Debug模式输出到控制台
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化日志(含时间戳、线程信息)
|
||||
private static String formatLog(LogLevel level, String tag, String name, String message) {
|
||||
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(new Date());
|
||||
String threadName = Thread.currentThread().getName();
|
||||
return String.format("%s [%s] %s/%s: %s %s\n", time, threadName, level.name(), tag, name, message);
|
||||
}
|
||||
|
||||
// 写入日志文件
|
||||
private static void writeToFile(String logMsg) {
|
||||
if (logFile == null) return;
|
||||
try (FileWriter writer = new FileWriter(logFile, true)) {
|
||||
writer.append(logMsg);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "File write failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 输出到Android控制台(Logcat)
|
||||
private static void outputToConsole(LogLevel level, String tag, String name, String message) {
|
||||
switch (level) {
|
||||
case DEBUG:
|
||||
Log.d(tag, name + " : " + message);
|
||||
break;
|
||||
case INFO:
|
||||
Log.i(tag, name + " : " + message);
|
||||
break;
|
||||
case WARN:
|
||||
Log.w(tag, name + " : " + message);
|
||||
break;
|
||||
case ERROR:
|
||||
Log.e(tag, name + " : " + message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 快捷调用方法(自动使用类名作为TAG)
|
||||
public static void d(Object context, String name, String message) {
|
||||
log(LogLevel.DEBUG, context.getClass().getSimpleName(), name, message);
|
||||
}
|
||||
|
||||
public static void i(Object context, String name, String message) {
|
||||
log(LogLevel.INFO, context.getClass().getSimpleName(), name, message);
|
||||
}
|
||||
|
||||
public static void w(Object context, String name, String message) {
|
||||
log(LogLevel.WARN, context.getClass().getSimpleName(), name, message);
|
||||
}
|
||||
|
||||
public static void e(Object context, String name, String message) {
|
||||
log(LogLevel.ERROR, context, name, message);
|
||||
}
|
||||
|
||||
public static void d(Object context, String name) {
|
||||
log(LogLevel.DEBUG, context.getClass().getSimpleName(), name, "");
|
||||
}
|
||||
|
||||
public static void i(Object context, String name) {
|
||||
log(LogLevel.INFO, context.getClass().getSimpleName(), name, "");
|
||||
}
|
||||
|
||||
public static void w(Object context, String name) {
|
||||
log(LogLevel.WARN, context.getClass().getSimpleName(), name, "");
|
||||
}
|
||||
|
||||
public static void e(Object context, String name) {
|
||||
log(LogLevel.ERROR, context.getClass().getSimpleName(), name, "");
|
||||
}
|
||||
}
|
||||
51
app/src/main/java/com/ttstd/dialer/utils/NativeUtils.java
Normal file
51
app/src/main/java/com/ttstd/dialer/utils/NativeUtils.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package com.ttstd.dialer.utils;
|
||||
|
||||
public class NativeUtils {
|
||||
static {
|
||||
System.loadLibrary("ttstd");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* sn加密key
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static native String getKey();
|
||||
|
||||
/**
|
||||
* 获取 获取绑定状态的code
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static native String getBindStatuSigCode();
|
||||
|
||||
/**
|
||||
* 和风天气API Host
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static native String getQWeatherUrl();
|
||||
|
||||
/**
|
||||
* 和风天气私钥
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static native String getQWeatherPrivateKey();
|
||||
|
||||
/**
|
||||
* 和风天气项目ID
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static native String getQWeatherProjectId();
|
||||
|
||||
/**
|
||||
* 和风天气凭据ID
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static native String getQWeatherCredentialId();
|
||||
|
||||
}
|
||||
@@ -1,12 +1,61 @@
|
||||
package com.ttstd.dialer.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
public class SystemUtils {
|
||||
|
||||
/**
|
||||
* 获取设备序列号
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@SuppressLint("MissingPermission")
|
||||
public static String getSerial() {
|
||||
String serial = "unknow";
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {//9.0+
|
||||
serial = Build.getSerial();
|
||||
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {//8.0+
|
||||
serial = Build.SERIAL;
|
||||
} else {//8.0-
|
||||
Class<?> c = Class.forName("android.os.SystemProperties");
|
||||
Method get = c.getMethod("get", String.class);
|
||||
serial = (String) get.invoke(c, "ro.serialno");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("e", "读取设备序列号异常:" + e.toString());
|
||||
}
|
||||
return serial;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统配置信息
|
||||
*
|
||||
* @param key
|
||||
* @param defaultValue
|
||||
* @return
|
||||
*/
|
||||
public static String getProperty(String key, String defaultValue) {
|
||||
String value = defaultValue;
|
||||
try {
|
||||
Class<?> c = Class.forName("android.os.SystemProperties");
|
||||
Method get = c.getMethod("get", String.class, String.class);
|
||||
value = (String) (get.invoke(c, key, "unknown"));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isMainProcessName(Context cxt, int pid) {
|
||||
String packageName = cxt.getPackageName();
|
||||
ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
@@ -21,4 +70,6 @@ public class SystemUtils {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
50
app/src/main/jni/ttstd.cpp
Normal file
50
app/src/main/jni/ttstd.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include <jni.h>
|
||||
#include <string>
|
||||
#include <sys/system_properties.h>
|
||||
// 日志打印
|
||||
#include <android/log.h>
|
||||
|
||||
#define LOG_TAG "TAG_LOG"
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_ttstd_dialer_utils_NativeUtils_getKey(JNIEnv *env, jclass clazz) {
|
||||
std::string key = "Ls0HSh9fNfeZ8Qu5";
|
||||
return env->NewStringUTF(key.c_str());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_ttstd_dialer_utils_NativeUtils_getBindStatuSigCode(JNIEnv *env, jclass clazz) {
|
||||
std::string key = "tongtongstudio";
|
||||
return env->NewStringUTF(key.c_str());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_ttstd_dialer_utils_NativeUtils_getQWeatherUrl(JNIEnv *env, jclass clazz) {
|
||||
std::string key = "nr4wcrp6av.re.qweatherapi.com";
|
||||
return env->NewStringUTF(key.c_str());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_ttstd_dialer_utils_NativeUtils_getQWeatherPrivateKey(JNIEnv *env, jclass clazz) {
|
||||
std::string key = "MC4CAQAwBQYDK2VwBCIEIBSTXI5L8P9x6fhhFn7zC82dMkDisIqb/QBlUOU20YTl";
|
||||
return env->NewStringUTF(key.c_str());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_ttstd_dialer_utils_NativeUtils_getQWeatherProjectId(JNIEnv *env, jclass clazz) {
|
||||
std::string key = "44KXE67QXE";
|
||||
return env->NewStringUTF(key.c_str());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_ttstd_dialer_utils_NativeUtils_getQWeatherCredentialId(JNIEnv *env, jclass clazz) {
|
||||
std::string key = "KB5C8PK5TY";
|
||||
return env->NewStringUTF(key.c_str());
|
||||
}
|
||||
114
app/src/main/res/layout/activity_weather_main.xml
Normal file
114
app/src/main/res/layout/activity_weather_main.xml
Normal file
@@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".activity.weather.main.WeatherMainActivity">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="click"
|
||||
type="com.ttstd.dialer.activity.weather.main.WeatherMainActivity.BtnClick" />
|
||||
|
||||
<variable
|
||||
name="weatherNow"
|
||||
type="com.qweather.sdk.response.weather.WeatherNow" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black">
|
||||
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/coordinatorLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="180dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_location"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="30sp"
|
||||
tools:text="北京" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_temp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="4dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:text="@{weatherNow.temp+`°`}"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="40sp"
|
||||
tools:text="N/A°" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_weather_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:text="@{weatherNow.text}"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
tools:text="晴" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_temp_range"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
tools:text="最高10° 最低0°" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/coordinatorLayout">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
@@ -24,31 +24,149 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="2"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:shadowColor="#80000000"
|
||||
android:shadowDx="2"
|
||||
android:shadowDy="2"
|
||||
android:shadowRadius="2"
|
||||
android:text="00:00"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="55sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="00:00" />
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<TextView
|
||||
android:id="@+id/tv_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:shadowColor="#80000000"
|
||||
android:shadowDx="2"
|
||||
android:shadowDy="2"
|
||||
android:shadowRadius="2"
|
||||
android:text="00:00"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="55sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="00:00" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_week"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:shadowColor="#80000000"
|
||||
android:shadowDx="2"
|
||||
android:shadowDy="2"
|
||||
android:shadowRadius="2"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="星期一" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_lunar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:shadowColor="#80000000"
|
||||
android:shadowDx="2"
|
||||
android:shadowDy="2"
|
||||
android:shadowRadius="2"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="正月初一" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:onClick="@{click::openWeather}"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_location"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:shadowColor="#80000000"
|
||||
android:shadowDx="2"
|
||||
android:shadowDy="2"
|
||||
android:shadowRadius="2"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="北京市" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_temp_current"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:shadowColor="#80000000"
|
||||
android:shadowDx="2"
|
||||
android:shadowDy="2"
|
||||
android:shadowRadius="2"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="N/A°" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_weather"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:shadowColor="#80000000"
|
||||
android:shadowDx="2"
|
||||
android:shadowDy="2"
|
||||
android:shadowRadius="2"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="晴" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_temp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:shadowColor="#80000000"
|
||||
android:shadowDx="2"
|
||||
android:shadowDy="2"
|
||||
android:shadowRadius="2"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="最低0° 最高10°" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
88
app/src/main/res/layout/item_weather.xml
Normal file
88
app/src/main/res/layout/item_weather.xml
Normal file
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<View
|
||||
android:id="@+id/view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@color/gray"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/view">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:maxLines="1"
|
||||
android:minEms="4"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="今天" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/tv_date"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginEnd="48dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/iv_icon">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_temp_min"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="0°" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_temp_max"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="10°" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -22,4 +22,13 @@
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
<item name="android:fitsSystemWindows">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppThemeBlack" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/black</item>
|
||||
<item name="colorPrimaryDark">@color/black</item>
|
||||
<item name="colorAccent">@color/black</item>
|
||||
<item name="android:fitsSystemWindows">true</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -24,6 +24,13 @@ buildscript {
|
||||
}
|
||||
|
||||
allprojects {
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
force 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
force 'androidx.annotation:annotation:1.1.0'
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
// mavenCentral()
|
||||
|
||||
Reference in New Issue
Block a user